Classic Computer Magazine Archive COMPUTE! ISSUE 133 / SEPTEMBER 1991 / PAGE 70

Global warnings. (programming style)
by Tom Campbell

If you're a regular reader of this column, you're familiar with my diatribes against global variables: They make code harder to maintain and reuse, they can be dangerous, and so on. But globals offer an important feature that local variables don't: They retain their values between subroutine calls.

Of course, this can be a trap. You may find yourself dealing with an unexpected bug (then again, how many of them are expected?) in which the value of a global is supposed to change but doesn't. It can be a long time before you discover that you've created a local variable in one of your routines with the same name as the global.

Another example: In some multitasking environments, where several copies of the same program can be running at the same time, the global variable space, like the code, is a share resource. So if you've written a word processor and user A adjusts the tab settings while user B is painstakingly editing a complex table, user B's work might suddenly take a nasty turn for the worse the next time the display is reformatted, because you've put the tab settings in a global variable.

Closer to home is Microsoft C, which relied on globals to such an extent that a huge number of its library routines, including common ones like scanf(), didn't work under Windows or OS/2 because they multitask. The list of verboten routines dwindles with each release of the compiler, but the lesson remains--not even the biggest developers are immune to questionable programming practices.

Disregading for the moment that you probably program on a PC and don't have to worry about multiple users, there's a third kind of variable called static that gives you the best of both worlds. It has the same scope as a local variable, yet it saves the value of that variable between subroutine calls. Statics may be initialized when declared, but the initialization is performed only once--not each time the routine is called. The name static is a C term; Turbo Pascal gives these variables the confusing moniker typed constants. They're declared in the CONST section, yet, unlike normal constants, their values may be altered.

How can statics coexist with the apparently exclusive global and local types? By trickery in the compiler.

Local variables are kept on the stack, which on many machines (80x86 machines among them) can be just about anywhere in memory. Even between invocations of the same routine, that routine's stack can be in a different place, and it's tracked by a variable on the CPU called the stack pointer. Since the same local variable could be stored in a hundred different places inside of a second, you can't rely on its value.

Globals, on the other hand, sit placidly in the same place all the time. The compiler itself is therefore written to "remember" the name of a global variable during the entire compilation. It remembers a local variable only while it's generating code for the routine in which that variable was declared (as well as any nested routines, in the case of Pascal) and lets the stack pointer track its physical location in memory at runtime. As you've probably guessed, a static is stored in the same area of memory as global data, but its name is only meaningful to the compiler while the routine it was declared in is being compiled.

This month's program is ONPATH.EXE, which lets you find all files on the path that meet the file specification you give it. To use it, enter ONPATH filespec at the command line. Here are some examples:


Don't give it a leading directory name, as in ONPATH C:\DOS\LINK.*. It won't work right, and if you want to restrict your search to a directory, you might as well use DIR.

The first example finds all files starting with TLINK and ending with any extension. For example, my path is PATH=E:\T\BAT;E:\BIN:C\DOS; E:\WORDS;C:\WINDOWS; E:\BORLANDC\BIN;E:\GEO and the search for TLINK on my machine turned up this disquieting result:

E:\BIN\TLINK.EXE 53414 05-07-90 02:00a E:\BORLANDC\BIN\TLINK.EXE 72585 02-13-91 02:00a E:\BORLANDC\BIN\TLINK.CFG 19 03-2491 01:52a

3 File(s) found.

When dozens of strange errors popped up in a program after I installed a new compiler, I discovered I needed ONPATH. The same program had compiled just fine before. You can see the culprit above. Two different versions of the linker. Since E:\BIN came before E:\BORLANDC\BIN on the path, the older linker was being run first.

The second example, using the filespec COMMAND.COM, will find all copies of COMMAND.COM on your path. I found three versions of COMMAND, from DOS versions 2.0, 3.2, and 3, on the path of one machine (not mine!). Note that, like DIR, ONPATH appends "*" as the extension if you don't provide one. ONPATH's output is redirectable, as you can see in the third example.

A static variable is used in the routine NextDirOnPath, which is passed a string to which it writes the name of the next directory on the path. If you have the very short path C:\DOS;E:\BIN, the first call will write C:\DOS to its Result parameter.

Note that NextDirOnPath thoughtfully removes trailing semicolons and backslashes. The second call writes E:\BIN to Result, and the last call returns, an empty string. NextDirOnPath must obviously save the ever-shrinking path somewhere, yet ONPATH.PAS sports no global variables.

NextDirPath works its magic by storing the path in a typed constant. Turbo Pascal's confusing name for static variables. The first time NextDirOnPath is called, the input variable Result is empty, so Turbo's GetEnv function is used to extract the PATH variable from your DOS environment, whereupon it's written to the static variable (or typed constant) SavedPath.

On later calls to NextDirOnPath, Result will be a nonempty value, so GetEnv is only called the first time. A search now commences for the first semicolon. When it's found, the path up to that point is copied into Result, it's amputated from the beginning of SavedPath, and the surviving path is automatically available for the next call to NextDirOnPath. You could have a global named SavedPath and variables named SavedPath in every routine, yet the static variable SavedPath would retain its value and be visible to this routine and this one only.