Page 3 of 3
After considering the elevated topic of objects and namespaces let's return to the nitty gritty of variables and their scope.
You might think that local or global variables were the only two possibilities but there is another.
If the language that you are using has a block structure, and most modern languages do, then you can use the block structure to determine the scope.
The most extreme form of block structure is where a procedure can have procedures defined within it and they can have procedures defined with in them and so on. This is a form of nesting of one procedure inside another that most object oriented languages have given up - it isn't particularly useful when you have the range of organisational schemes that objects give you. However, even object oriented languages allow you to nest the definition of a class within another class and even allow nested blocks of code within a method or function.
If you do write programs using block structure then an easy to implement and understand scope rule is that a variable is defined in any blocks defined within the block that the variable is declared in.
This produces a sort of halfway between a local and a global variable. The variable is local to all blocks that are defined outside of the block that the variable is declared in but global to all blocks defined within the block that declares the variable.
You can also think of local and global variables as being special cases of the nested scope rule. Global variables are simply variables declared in a block that contains every other block and local variables are declared in a block that contains only the procedure that they are local to.
For example, consider the following short extract:
private void MyMethod()
int i = 1;
i = 2;
The use of code blocks within a method is so rare that you might not even recognise this as valid code - it's C# and it is valid. There are two code blocks defined within the method. The variable i is declared in the first block and its scope is the first block and all blocks that it contains. Thus the assignment in the inner second block is valid and it assigns 2 to the variable i declared in the outer block. Notice that the method doesn't have a variable called i defined in its scope. That is you try to use a variable called i within the method i.e. outside of the block it is declared in then an error results.
Now consider the following:
int i = 1;
i = 2;
int i = 4;
This too is perfectly valid in C#. The variable i declared in the final block is only in scope within its block and it doesn't interfere or clash with the i defined in the first block.
When you are first learning any block structured language the scope rules seem a bit complicated and so there is a tendency to not make use of them and concentrate on learning the language. After a while you end up all but forgetting them, with only the occasional memory jog from reading the bit in the manual every time a new version of the language appears.
However, from a more sophisticated point of view the way that the scope rule works is very clever and very close to the way that you actually want to work most of the time. It also it a bit like an inheritance mechanism for data, each inner block inheriting all of the outer blocks variables and procedures.
The final twist to the story is visibility.
Suppose a variable is in scope but you then declare another variable of the same name in an inner block. Presumably you would like to work with the newly declared variable and not the one defined in the outer block. The outer block's version of the variable is said to be in scope but inaccessible or not visible and the new definition replaces it. You can invent additional rules to make variables that are in scope but redefined visible. For example, you could insist that every block was named and then you could refer to a variable as blockname.variablename. If you leave off the blockname then it defaults to the current block. If this sounds familiar then I am not surprised because it is exactly the mechanism that object oriented languages used for making clear which object method is to be used in an object hierarchy.
However, this said, many language designers have decided that such a name collision is not a good idea and will flag any attempt to declare a variable within an inner block that is already in scope as an error. For example, in C# the following is an error:
int i = 1;
int i = 2;
Existence is an aspect of variables that most programmers never consider because it is an obvious consequence of the type of language that they most use.
For example, in Fortran and Basic variables are usually static, in block structured languages such as Java, C and C# they are usually dynamic.
- A static variable is one that exists for the duration of the program.
- A dynamic variable is created each time a procedure is run and then destroyed when it ends.
In languages such as Fortran and Basic static variables are the norm.
In block structured languages dynamic variables are the norm.
Static variables can be used to store state information such as the number of times a procedure has been called. Dynamic variables minimise storage requirements by assuming that if a procedure isn't running then its local variables cannot be in use by any other procedure.
Of course in a block structured language dynamic variables exist when the block they are declared in is executing.
This existence rule is just an extension of the nested scope rule - if a variable is in scope then it exists if it isn't then it doesn't.
Even block structured languages normally allow the programmer to create static variables on demand. Usually by declaring them using a a STATIC modifier. Languages that don't have a STATIC modifier and so don't appear to recognise the existence of static variables allow you to create static variables as global variables. A variable that has global scope will exist for the whole program and so can be described as a global static variable.
This is of course the trick used in both C++ and Object oriented Pascal to ensure that objects exist for the duration of the program - they are all declared at the highest level in the program!
You might be wondering what the advantage of static variables is that many early languages adopted them as the only type they provided? Well the answer is that a static variable can be assigned to an area of storage by the compiler at compile time and it stays there at the same location throughout the run of the program. This makes static variables simpler and faster than dynamic variables.
Dynamic variables on the other hand cannot be allocated at compile time they have to allocated to storage at run time. Fortunately this isn't as difficult as it sounds because most block structured languages use some sort of execution stack.
When a new procedure is started the old variables are pushed on the stack and the new procedures are allocated. When the procedure is finished the original procedure's variables are pulled off the stack.
This distinction between static and dynamic can also be phrased in terms of late and early binding. These terms are usually applied to object methods. Should a method's name be bound to the code at compile time, i.e. early binding, or at run time, i.e. late binding. Another way of saying this is should a method or procedure be static or dynamic?
At this point you can now see that variables can be classified in two ways Scope - local, nested, global; and Existence - static and dynamic.
No doubt there are other subtle ways of managing variables their scope, visibility, lifetime and so on - but this more or less corresponds to what most programmers have to deal with today.