Webster Home Page

8.5 Local Variables

HLA procedures, like procedures and functions in most high level languages, let you declare local variables. Local variables are generally accessible only within the procedure, they are not accessible by the code that calls the procedure. Local variable declarations are identical to variable declarations in your main program except, of course, you declare the variables in the procedure's declaration section rather than the main program's declaration section. Actually, you may declare anything in the procedure's declaration section that is legal in the main program's declaration section, including constants, types, and even other procedures1. In this section, however, we'll concentrate on local variables.

Local variables have two important attributes that differentiate them from the variables in your main program (i.e., global variables): lexical scope and lifetime. Lexical scope, or just scope, determines when an identifier is usable in your program. Lifetime determines when a variable has memory associated with it and is capable of storing data. Since these two concepts differentiate local and global variables, it is wise to spend some time discussing these two attributes.

Perhaps the best place to start when discussing the scope and lifetimes of local variables is with the scope and lifetimes of global variables -- those variables you declare in your main program. Until now, the only rule you've had to follow concerning the declaration of your variables has been "you must declare all variables that you use in your programs." The position of the HLA declaration section with respect to the program statements automatically enforces the other major rule which is "you must declare all variables before their first use." With the introduction of procedures, it is now possible to violate this rule since (1) procedures may access global variables, and (2) procedure declarations may appear anywhere in a declaration section, even before some variable declarations. The following program demonstrates this source code organization:

program demoGlobalScope;
#include( "stdlib.hhf" );

    AccessibleInProc: char;
    procedure aProc;
    begin aProc;
        mov( `a', AccessibleInProc );
    end aProc;
    InaccessibleInProc: char;
begin demoGlobalScope;


    mov( `b', InaccessibleInProc );
        "AccessibleInProc   = `", AccessibleInProc,   "`" nl
        "InaccessibleInProc = `", InaccessibleInProc, "`" nl
end demoGlobalScope;

Program 8.5	 Demonstration of Global Scope

This example demonstrates that a procedure can access global variables in the main program as long as you declare those global variables before the procedure. In this example, the aProc procedure cannot access the InaccessibleInProc variable because its declaration appears after the procedure declaration. However, aProc may reference AccessibleInProc since it's declaration appears before the aProc procedure in the source code.

A procedure can access any STATIC, STORAGE, or READONLY object exactly the same way the main program accesses such variables -- by simply referencing the name. Although a procedure may access global VAR objects, a different syntax is necessary and you need to learn a little more before you will understand the purpose of the additional syntax. Therefore, we'll defer the discussion of accessing VAR objects until the chapters dealing with Advanced Procedures.

Accessing global objects is convenient and easy. Unfortunately, as you've probably learned when studying high level language programming, accessing global objects makes your programs harder to read, understand, and maintain. Like most introductory programming texts, this text will discourage the use of global variables within procedures. Accessing global variables within a procedure is sometimes the best solution to a given problem. However, such (legitimate) access typically occurs only in advanced programs involving multiple threads of execution or in other complex systems. Since it is unlikely you would be writing such code at this point, it is equally unlikely that you will absolutely need to access global variables in your procedures so you should carefully consider your options before accessing global variables within your procedures2.

Declaring local variables in your procedures is very easy, you use the same declaration sections as the main program: STATIC, READONLY, STORAGE, and VAR. The same rules and syntax for the declaration sections and the access of variables you declare in these sections applies in your procedure. The following example code demonstrates the declaration of a local variable.

program demoLocalVars;
#include( "stdlib.hhf" );

    // Simple procedure that displays 0..9 using
    // a local variable as a loop control variable.
    procedure CntTo10;
        i: int32;
    begin CntTo10;
        for( mov( 0, i ); i < 10; inc( i )) do
            stdout.put( "i=", i, nl );
    end CntTo10;
begin demoLocalVars;

end demoLocalVars;

Program 8.6	 Example of a Local Variable in a Procedure

Local variables you declare in a procedure are accessible only within that procedure3. Therefore, the variable i in procedure CntTo10 in Program 8.6 is not accessible in the main program.

HLA relaxes, somewhat, the rule that identifiers must be unique in a program for local variables. In an HLA program, all identifiers must be unique within a given scope. Therefore, all global names must be unique with respect to one another. Similarly, all local variables within a given procedure must have unique names but only with respect to other local symbols in that procedure. In particular, a local name may be the same as a global name. When this occurs, HLA creates two separate variables for the two objects. Within the scope of the procedure any reference to the common name accesses the local variable; outside that procedure, any reference to the common name references the global identifier. Although the quality of the resultant code is questionable, it is perfectly legal to have a global identifier named MyVar with the same local name in two or more different procedures. The procedures each have their own local variant of the object which is independent of MyVar in the main program. Program 8.7 provides an example of an HLA program that demonstrates this feature.

program demoLocalVars2;
#include( "stdlib.hhf" );

    i:  uns32 := 10;
    j:  uns32 := 20;
    // The following procedure declares "i" and "j"
    // as local variables, so it does not have access
    // to the global variables by the same name.
    procedure First;
        i: int32;
    begin First;
        mov( 10, j );
        for( mov( 0, i ); i < 10; inc( i )) do
            stdout.put( "i=", i," j=", j, nl );
            dec( j );
    end First;
    // This procedure declares only an "i" variable.
    // It cannot access the value of the global "i"
    // variable but it can access the value of the
    // global "j" object since it does not provide
    // a local variant of "j".
    procedure Second;
    begin Second;
        mov( 10, j );
        for( mov( 0, i ); i < 10; inc( i )) do
            stdout.put( "i=", i," j=", j, nl );
            dec( j );
    end Second;
begin demoLocalVars2;

    // Since the calls to First and Second have not
    // modified variable "i", the following statement
    // should print "i=10".  However, since the Second
    // procedure manipulated global variable "j", this
    // code will print "j=0" rather than "j=20".
    stdout.put(  "i=", i, " j=", j, nl );
end demoLocalVars2;

Program 8.7	 Local Variables Need Not Have Globally Unique Names

There are good and bad points to be made about reusing global names within a procedure. On the one hand, there is the potential for confusion. If you use a name like ProfitsThisYear as a global symbol and you reuse that name within a procedure, someone reading the procedure might think that the procedure refers to the global symbol rather than the local symbol. On the other hand, simple names like i, j, and k are nearly meaningless (almost everyone expects the program to use them as loop control variables or for other local uses), so reusing these names as local objects is probably a good idea. From a software engineering perspective, it is probably a good idea to keep all variables names that have a very specific meaning (like ProfitsThisYear) unique throughout your program. General names, that have a nebulous meaning (like index, counter, and names like i, j, or k) will probably be okay to reuse as global variables

There is one last point to make about the scope of identifiers in an HLA program: variables in separate procedures (that is, two procedures where one procedure is not declared in the declaration section of the second procedure) are separate, even if they have the same name. The First and Second procedures in Program 8.7, for example, share the same name (i) for a local variable. However, the i in First is a completely different variable than the i in Second.

The second major attribute that differentiates (certain) local variables from global variables is lifetime. The lifetime of a variable spans from the point the program first allocates storage for a variable to the point the program deallocates the storage for that variable. Note that lifetime is a dynamic attribute (controlled at run time) whereas scope is a static attribute (controlled at compile time). In particular, a variable can actually have several lifetimes if the program repeatedly allocates and then deallocates the storage for that variable.

Global variables always have a single lifetime that spans from the moment the main program first begins execution to the point the main program terminates. Likewise, all static objects have a single lifetime that spans the execution of the program (remember, static objects are those you declare in the STATIC, READONLY, or STORAGE sections). This is true even for procedures. So there is no difference between the lifetime of a local static object and the lifetime of a global static object. Variables you declare in the VAR section, however, are a different matter. VAR objects use automatic storage allocation. Automatic storage allocation means that the procedure automatically allocates storage for a local variable upon entry into a procedure. Similarly, the program deallocates storage for automatic objects when the procedure returns to its caller. Therefore, the lifetime of an automatic object is from the point the procedure is first called to the point it returns to its caller.

Perhaps the most important thing to note about automatic variables is that you cannot expect them to maintain their values between calls to the procedure. Once the procedure returns to its caller, the storage for the automatic variable is lost and, therefore, the value is lost as well. Therefore, you must always assume that a local VAR object is uninitialized upon entry into a procedure; even if you know you've called the procedure before and the previous procedure invocation initialized that variable. Whatever value the last call stored into the variable was lost when the procedure returned to its caller. If you need to maintain the value of a variable between calls to a procedure, you should use one of the static variable declaration types.

Given that automatic variables cannot maintain their values across procedure calls, you might wonder why you would want to use them at all. However, there are several benefits to automatic variables that static variables do not have. The biggest disadvantage to static variables is that they consume memory even when the (only) procedure that references them is not running. Automatic variables, on the other hand, only consume storage while there associated procedure is executing. Upon return, the procedure returns any automatic storage it allocated back to the system for reuse by other procedures. You'll see some additional advantages to automatic variables later in this chapter.

8.6 Other Local and Global Symbol Types

As mentioned in the previous section, HLA lets you declare constants, values, types, and anything else legal in the main program's declaration section within a procedure's declaration section. The same rules for scope apply to these identifiers. Therefore, you can reuse constant names, procedure names, type names, etc. in local declarations (although this is almost always a bad idea).

Referencing global constants, values, and types, does not present the same software engineering problems that occur when you reference global variables. The problem with referencing global variable is that a procedure can change the value of a global variable in a non-obvious way. This makes programs more difficult to read, understand, and maintain since you can't often tell that a procedure is modifying memory by looking only at the call to that procedure. Constants, values, types, and other non-variable objects, don't suffer from this problem because you cannot change them at run-time. Therefore, the pressure to avoid global objects at nearly all costs doesn't apply to non-variable objects.

Having said that it's okay to access global constants, types, etc., it's also worth pointing out that you should declare these objects locally within a procedure if the only place your program references such objects is within that procedure. Doing so will make your programs a little easier to read since the person reading your code won't have to search all over the place for the symbol's definition.

1The chapter on Advanced Procedures discusses the concept of local procedures in greater detail.

2Note that this argument against accessing global variables does not apply to other global symbols. It is perfectly reasonable to access global constants, types, procedures, and other objects in your programs.

3Strictly speaking, this is not true. The chapter on Advanced Procedures will present an exception.

Web Site Hits Since
Jan 1, 2000