Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Tools

Structured Programming


JAN93: STRUCTURED PROGRAMMING

Seventh Son

Last week the Magic Van flipped over 100,000 on the way to Safeway for some ice and a paper, and almost immediately I started hearing creaks and groans I'm sure I never heard before. But I sympathize; having turned 40 myself this past summer, I've begun to know what creaks are. (Groans I figure I won't encounter for another 20 years.) And just as youth is wasted on the young, new-car smell is wasted on a pile of metal just out of the factory, that may or may not be a lemon and self-destruct in your driveway. I wasn't sure I was going to keep the Magic Van until it roared past 50,000, carrying the normal American load of dog-puke stains, gravel dings, and melted Jolly Joes in the upholstery. Now, I'm quite sure I'll keep it forever.

I kind of hope Carol feels the same way about me.

I was forced to face another milestone this week, that seemed a little unreal somehow: Turbo Pascal is now nine years old. Could it be? Then again, I still had most of my hair when Version 1.0 and I met, and I was still a Cobol slave for Xerox. Time passes.

On Turbo Pascal's ninth birthday (or pretty close to it) Philippe Kahn released his seventh son upon the world. It took me all of, oh, ten minutes to become a believer. There has been a time or two (or maybe three) when I thought I might trade in Turbo Pascal for something else. But having come this far, I suspect I'll keep it forever.

Let me take a little time to tell you why.

The Long and the Short Of It

With Version 7, Borland has brought Turbo Pascal into line with its longstanding strategy for C++ products: a low-end "Turbo" version and a high-end "Borland" version. The idea has been to have a sleek, uncomplicated compiler for hobbyist programmers, and a soup-to-nuts development package for people who do it for a living. This has worked well in the C++ world; Borland has made a lot of money upgrading people from Turbo C++ to Borland C++. I see no reason why it won't work just as well in the Pascal marketplace.

So the big Pascal picture from Borland looks like this: Borland Pascal with Objects 7.0 (BP7 for short) may be hosted on and develops code for both Windows 3.x and DOS. DOS support includes both real mode and DPMI (DOS protected-mode interface). It provides everything Turbo Pascal 6.0 and Turbo Pascal for Windows 1.5 provide, and then some. (I'll get to the "and then some" in a bit.) On the low end, Turbo Pascal 7.0 replaces Turbo Pascal 6.0, and is hosted on and develops code for DOS real mode only. Turbo Pascal for Windows 1.5 remains unchanged and on the market, and is hosted on and develops code for Windows 3.x only.

BP7 includes both Turbo Vision and Object Windows Library, plus the source for both frameworks and the runtime libraries generally. There's a standard passel of new utilities (most of them for the Windows platform) and 3800 pages of documentation in 11 manuals. (This is yet another "hernia-pack" compiler--big as a carry-on travel bag and heavier than Mr. Byte, who readily admits to having had a few too many Milk Bones in his long life.)

The additions to Turbo Pascal 7.0 are more modest, but still significant. The single most irritating problem with Turbo Pascal is now history: The IDE and the compiler can now use all available extended memory on 286/386/486 machines. No more "Out of Memory" message boxes, ever again! With memory running about $25.00 a megabyte, you might as well pour 8 or 12 megs into the box and be done with it. Ariel, my new home machine, has 16 Mbytes now, and it's kind of like moving from an army cot to a king-sized waterbed. The comfort factor is simply beyond quantification, especially now that it extends to my favorite compiler. (Keep in mind that for Turbo Pascal, this applies only to the IDE. The .EXE files generated by Turbo Pascal 7.0 do not make use of extended memory! For that you need the high-end Borland Pascal with Objects 7.0 package, as I'll explain below.) The IDE will still operate in real mode on pre-286 CPUs.

New optimizations, an enhanced Turbo Vision, and a syntax-cognizant editor with undo and redo are the other obvious additions to both products. We'll come back to these and the other minor enhancements, some of which, while minor, are interesting indeed.

The Big, Big Win

But beyond anything else in the release of BP7, the big, big win is DPMI support. Over the past two or three years, protected-mode capability has become the dividing line between the Big Guys and the Little Guys in compiler-land. We've beaten some cracks in the 640K memory barrier over the years and finessed it with overlays and EMS support, but this is the real thing: True, unadulterated and uncompromised access to as much as 16 Mbytes of RAM. Pascal is in with the big guys now.

Alas, along with power comes the inevitable responsibility, and also a need to recognize that things are just not going to be simple anymore. (A minicomputer bigot in my acquaintance said not long ago that the pre-Windows 3.0 decade of 1981-1991 was "the longest-running fools' paradise in the history of computing." He was right--though he'll gag before admitting that we fools changed the shape of history and now, paradise having been lost, are still running the asylum.) Programming for DPMI is not fundamentally difficult, but it is by no means as simple as Borland's BP7 documentation implies--and contrary to their claims, it broke nearly every one of the substantial Turbo Pascal real-mode programs I threw at it. My early experience suggests that if you're going to grab for those 16 Mbytes worth of gold rings, you'd best understand the engine that drives the carousel.

DPMI is, at the bottom of things, just an API. It is an interface spec that allows programs to allocate, use, and release extended memory in protected mode. A DPMI implementation (as opposed to the spec) is a collection of callable routines that gather all the low-level management of protected-mode memory into one place. Rather than the word "implementation," you'll hear talk of something called a DPMI server. As the word "server" implies, there is but one block of code in your system at any given time that has the power to control extended memory in protected mode. There can't be two. But to use the DPMI spec, there definitely has to be at least one.

This poses some problems. Windows 3.x, for example, incorporates a DPMI server. Other utilities and environments do as well. But if you're not using Windows or anything that includes a DPMI server, your application is going to have to load and run the one that Borland provides. Your protected-mode BP7 programs have to query the system to see if a DPMI server is already active before beginning execution. (Fortunately, this is done automatically by the RTL.) If no DPMI server is active, the program will load and run Borland's server before running the code that you've written.

It's actually more complex than even that. Borland has created a layer atop the "naked" DPMI server to broaden the capabilities of the DPMI idea in a Pascalish direction. This layer is called a run-time manager (RTM), and it is a separate executable program that comes in once a DPMI server has been detected or installed. The RTM allows the smooth creation of a Pascal megaheap that can make full use of protected-mode RAM, and also replaces the traditional Turbo Pascal overlay manager for executing overlaid applications in protected mode.

Most of the time, and with most protected-mode applications, all this grimbling of servers and layers and managers happens automatically, behind the scenes. You have to be sure that if no other DPMI server is active, your application can find and launch the two files that Borland supplies to provide DPMI services. That's easy enough.

The real trick to DPMI programming is being sure that your code doesn't commit any protected-mode no-nos.

Protected-mode Correctness

Compared to the fast-and-loose way the CPU lets us pulp-and-bale memory and registers in real mode, in protected mode the CPU is crankier than a Berkeley liberal with hemorrhoids. Certain things Just Aren't Done, and the bulk of the problems you'll have moving relatively simple applications to protected mode involve making sure all the rules are followed to the letter.

In general, your problems will fall into three major categories: specifying absolute addresses, below-the-belt pointer manipulation, and misuse of segment registers from assembly code.

The first category is where most of my own problems lay. Most of us do a fair amount of peeking at and poking to video memory, usually by using the MEM array. (Some of us do it by building a pointer to video RAM with the Ptr function, which belongs to the second category.) MEM requires a segment and an offset address, separated by a colon, and acting as an index into real-mode memory. Specifying offsets into segments is still legit, but specifying segments directly is verboten.

The problem is that in protected mode, segments aren't really segments anymore. What we on the application side of the machine might from realmode habit want to call a "segment" is actually a selector, which is used by the DPMI server to identify an actual segment value that is used to construct the physical address. Setting up segments is no longer the proper job of the application. The app asks the DPMI server to set up a group of segments, and then the server returns a selector for each segment. The selector is just a way to identify a given segment without actually using the segment's address as its name, as we do in real mode. The app never knows what the true, physical segment address for its selector really is; that's a secret that helps the server keep out-of-control programs from trashing all of memory. It also allows the operating system or some other control program to implement true virtual memory and do many other minicomputer-like things.

If you're in protected mode and use an expression like MEM[$B800:0], the CPU will assume that $B800 is supposed to be a selector. But if no selector has the value $B800, the CPU will issue a general-protection (GP) fault, which within Turbo Pascal's context is considered a runtime error and will halt your application.

So what do you do? Borland has anticipated this problem, and provides a number of predefined segment values that you can use in place of literal segment addresses. These variables have names like Seg0040, SegA000, SegB000, and SegB800. Their actual values are valid selectors returned by the DPMI server, but their names indicate what they point to. In other words, you would replace $B800 in your MEM statement with SegB800, and everything will be fine. MEM will return the exact same data byte, with the blessing of the DPMI server. If you need to use MEM to access other areas of memory not covered by any of the predefined selector variables, you'll have to learn how to ask the DPMI server nicely for a selector. It's not what I'd call obvious, but having licked Turbo Vision, I'd be loath to call it impossible.

I've never been a heavy user of the ABSOLUTE reserved word, and I doubt I'm going to use it much anymore. ABSOLUTE manipulates addresses, and it will crash you with a GP fault (just like MEM) if you try to use it with a physical address rather than selectors.

The second category of problems is direct manipulation of the interior components of pointers. If you try to create a pointer using the Ptr routine, you must, as with MEM and ABSOLUTE, be sure that the segment portion of the pointer that you're building is a valid selector. (Using the predefined selector variables like SegB800 is one safe way to do this.) Nor can you do "pointer arithmetic" anymore by acting directly on the segment. (Manipulating the offset is still OK.) Remember, the segment portion of a pointer isn't an address anymore; it's a selector, which is just a name or a handle, and incrementing or decrementing the selector just changes the name of the selector to one that probably doesn't exist.

Dereferencing a NIL pointer will also cause a GP fault, as will dereferencing a pointer that has been disposed of. This has the slightly weird effect of forcing the CPU to do some of your debugging for you--for instance, an application that used to work most of the time (that is, until dereferencing a dead pointer did something ugly) will now not work at all! After the initial embarrassment wears off, I suspect most of us will come to depend heavily on this little perk.

Finally, the gobble-uns'll getcha if you try to use any of the segment registers as just more buckets, even if, like ES, those registers have no dedicated higher purpose. The temptation to use ES as a scratch register in assembly routines is strong, especially in the middle of tight loops where going to memory can be very expensive in performance terms. But once you're in protected mode, no segment register may contain anything but a valid selector. If while in protected mode you stuff a value into ES that doesn't happen to correspond to a valid selector, you'll get a GP fault. That's part of what the "protected" in protected mode is about. Scan your ASM routines or your INLINE code before you try compiling for DPMI. You may be amazed at the segment register abuse that you uncover. Lord knows I was.

Now, what does this protected stuff buy you? Even in a machine with a paltry 4 Mbytes of RAM, I was able to create a heap with 1,168,848 bytes. And here at home on Ariel's 16 Mbytes, the heap comes to a thoroughly gaspable 4,143,888 bytes. One can do real things with that much heap! My only remaining question is why the heap tops out so soon. Why not an 8-Mbyte heap, or even a 12-Mbyte heap? Whether or not I disable my 4-Mbyte RAM drive, the most I can find amidst my 16 Mbytes is that 4,143,888 bytes. Something is topping the heap out at 4 Mbytes, and when I figure out what it is I will certainly report. (Keep in mind, it's now Thursday and I only received this critter on Monday!)

Break and Continue

Compared to the changes to the programming infrastructure, Borland's mods to the Pascal language definition itself are minor. But hey, what does Turbo Pascal really lack? They got it almost bang-on in 1983, and they've had nine years since to think it through. Two of the items that I've been demanding since the beginning are finally here: Break/Cycle and conformant arrays. And wouldn't you know it? They changed the names of both.

Academics are fond of saying that it is possible to exit any loop at any point in a structured manner by the proper use of flags and tests. That's true. And when I was a kid I would sit tailor-style in the schoolyard for what seemed like hours, picking knots and tangles out of kite string so I wouldn't lose any of it. This was dumb, although when I was penniless and didn't know any better, it made a certain frugal sense.

I used to pick my early way out of loops like an ant following a strand through a wad of tangled kite string. Then I got tired of the game and started using GOTO like a knife to cut the nonsense and get where I needed to go at once. It felt good. Now it's unnecessary. Both TP7 and BP7 support two new predefined procedures, Break and Continue. The Break procedure allows you to bust out of a loop at any time and resume execution at the first statement following the end of the loop. Continue is what I always knew as Cycle, which has been supported in Microsoft Pascal (not QuickPascal!) since the beginning. It cuts the current pass through the loop short, and resumes execution at the top of the loop with the next iterator if there is one. This applies to all loops: IF..THEN, WHILE..DO, and REPEAT..UNTIL.

Borland created Break and Continue as procedures, not reserved words, which I think was a good idea. Adding reserved words to a language necessarily (and irreversibly) breaks code, and should be done with the utmost hesitation. I haven't yet tried to trace the code to see precisely how they were implemented, but I expect they amount to something like the setjmp/longjmp pair that C hackers cherish. At the top and bottom of every loop, the code generator probably makes a call to setjmp; in a sense, to "mark the spot." Later, while inside the loop, the code can call Break or Continue to perform the longjmp to the respective target, either at the top of the loop (for Continue) or immediately after it (for Break).

Regardless of how it works under the sheets, with the addition of Break and Continue, there's very little reason left to use GOTO. Nonetheless, I speak of GOTO as the gun freaks speak of their guns: I hope never to have to use it, but when I need it I want it to be there.

Open Array Parameters

At Pascal's very inception, Niklaus Wirth knew that Pascal's rather strong typing could get seriously in the way of certain kinds of useful structures, including procedures that act upon arrays incorporating the same base type but having different bounds. He added the notion of conformant arrays to Pascal, which allows arrays of different bounds values to be passed as actual parameters within the identical formal parameter. The formal parameter is said to conform to the bounds of the actual array parameter passed in it, hence the name.

In TP/BP 7, a formal array parameter in a procedure or a function can be defined without any bounds at all, as shown in Listing One. The array can be passed either by value or by reference; it doesn't matter. The trick is that within the procedure, you can query the bounds of the actual parameter by using two predefined functions, Low and High. Low returns the low bound of the actual array parameter passed as its argument, and High returns the high bound. Once you obtain the bounds of the conformant array, you can manipulate it just as you would any array. Be careful not to create functions of your own named Low or High. In one of my test programs I used a unit that redefined the two bounds-query functions, and it took some head scratching to discover why conformant arrays suddenly refused to work.

Borland renamed this idea "open array parameters." Lord knows why Borland didn't just call them "conformant arrays," but I'd have kept them even if they were named Al Gore. Conformant arrays will be handy in this new protected-mode world, where it's no longer kosher to play indiscriminate pointer games to accomplish the same things.

And alas, as much as I had hoped otherwise, arrays are still limited to 64K in size, even in protected mode. Keep in mind, moving beyond 64K for individual Pascal data structures generally requires moving beyond 64K segments; the final release from 64K segments does not come until you move fully to "flat address" 32-bit mode, in which a segment may be any size up to 2{32} bytes. Maybe next version.

Keep it Forever

There you have it. For my money, this is the most significant upgrade of the product since Turbo Pascal 4.0; more significant, in fact, than the introduction of objects in 1989. Data structures (including objects) are intriguing, but you can do anything without objects that you can do with objects, albeit not always easily. Infrastructure improvements like DPMI access, on the other hand, can be go/no-go propositions. Certainly applications that simply could not be written before in Turbo Pascal, using any paradigm, can be written now.

There is another major infrastructure improvement that I haven't had time to test yet: DOS DLLs. I'll try to spend a little time on them next column. In the meantime, sheesh, I have some learning to do.



_STRUCTURED PROGRAMMING COLUMN_
by Jeff Duntemann


[LISTING ONE]
<a name="0053_000b">

PROGRAM ConformTest;

{ Demo program for Turbo Pascal 7.0 conformant arrays }
{ By Jeff Duntemann; From DDJ for January 1993        }

USES Crt;

VAR
  I     : Integer;
  Manny : ARRAY[36..72] OF Integer;
  Moe   : ARRAY[0..137] OF Integer;
  Jack  : ARRAY[1..40]  OF Integer;

{ Note that neither RandomFill nor ArrayMax specify the bounds  }
{ of their integer array parameter.  The High and Low functions }
{ are predefined but are *not* reserved words. }

PROCEDURE RandomFill(VAR Target : ARRAY OF Integer);

VAR
  I : Integer;
BEGIN
  Randomize;
  FOR I := Low(Target) TO High(Target) DO
    Target[I] := Random(100);
END;

FUNCTION ArrayMax(Target : ARRAY OF Integer) : Integer;

VAR
  I,Temp : Integer;
BEGIN
  Temp := 0;
  FOR I := Low(Target) TO High(Target) DO
    IF Target[I] > Temp THEN Temp := Target[I];
  ArrayMax := Temp;
END;

BEGIN
  ClrScr;
  RandomFill(Manny);
  RandomFill(Moe);
  RandomFill(Jack);
  Writeln('The largest element of Manny is ',ArrayMax(Manny));
  Writeln('The largest element of Moe is ',ArrayMax(Moe));
  Writeln('The largest element of Jack is ',ArrayMax(Jack));
  Readln;
END.












Copyright © 1993, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.