Matt Bishop teaches computer science at Dartmouth College and has used C extensively on many different types of UNIX-based systems. Before, he was a research scientist at the Research Institute for Advanced Computer Science. His research areas are computer security and software engineering, especially portability. He may be contacted at Department of Mathematics and Computer Science, Dartmouth College, Hanover, NH 03755 (603) 646-3267.
Unlike other high-level languages, C grants the programmer some control over where variables are placed over "storage." When a program defines a variable, the compiler decides how to store the value of that variable and creates a space in memory for the value. The compiler uses the declaration to determine how much space to allocate and how to allocate it as temporary storage (such as on a stack) or as more permanent storage (in data space).
The format of a C variable declaration is:
<storage-class specifier> <type identifier>The storage classes are static, auto, register, and extern. (A fifth keyword, typedef, is considered a storage class for syntactic reasons, but plays no part in anything that follows.)Each of the following sections discusses one type of storage declaration, its effects, and when it is legal. The last section looks at a simple program written in a variety of ways and shows how changing storage classes affects the way the compiler handles storage.
Auto
The simplest class of storage is automatic storage, indicated by the storage class keyword auto. Any variables defined at the beginning of a block with no explicit storage class specifier are assumed to be automatic. The storage for these variables is created when the block is entered, and exists until the block is left. For example, in Listing 1, the storage space for i is allocated when the routine is entered, and deallocated when the program leaves the routine.Most compilers keep autos in a stack a list onto which things are pushed (or put) and from which things are popped (or removed.) Because the last thing pushed onto the stack is the first thing removed, a stack is sometimes called a "LIFO" list (for "Last In, First Out").
Note that the storage class specifier is almost always omitted when a variable is automatic; since automatic variables only occur in blocks and never outside them, this does not pose any problems. However, another version of automatic storage, register requires a storage class specifier.
Register
Unlike with other storage class specifiers, compilers are at liberty to obey or ignore the register keyword. This storage class is the same as automatic so far as the programmer is concerned; however, rather than allocating storage on a stack (as with variables declared auto) the compiler arranges for the variables to be stored in CPU registers. Usually the machine can access data in the registers much faster than it can data on the stack.Because of the nature of machine registers, declaring a variable as a register variable entails some restrictions. Many machines cannot use the address of a register in the same way they use a memory address, so the address operator & cannot be applied to a register variable. Some compilers will not allow certain types of variables to be assigned to registers. Also, compilers accept only a limited number of register declarations (the number varies from machine to machine and even from compiler to compiler) and do not give messages indicating when the register keyword is being ignored.
In Listing 2 the variable ir is stored in the stack; however, the contents of address_of_ir will be put into a register. This register variable will be assigned as its value the address of ir. Note that references using pointers are legal with registers, so the printf will print the value stored in ir correctly. However, if ir were declared as a register variable rather than an auto, the compiler would have printed an error message for the line
address_of_ir = &ir;since that line involves taking the address of the register variable.Both register and auto classes are transient; they go away when the block finishes executing. But often a program needs values to remain throughout the life of the program; the next two sections deal with longer duration storage classes.
Extern
When applied to a variable, the storage class extern indicates that the definition of the variable is located in another file. Hence, extern statements are declarations giving just type information rather than definitions; the extern class does not cause any memory to be allocated.An extern declaration may appear anywhere before the declared variable is referenced. An extern declaration can even appear in the same file as the corresponding definition which supports the common practice of collecting all related extern declarations in a header file that is #included in all source files.
An extern function declaration indicates that the function is defined elsewhere. (The keyword extern is often omitted here.) The compiler uses this declaration for type information and nothing else. See the example in Listing 3.
When the compiler encounters a function, it assumes that function returns an integer value unless that function has been declared previously. So, in Listing 3 the compiler assumes sqrt() returns an integer and generates code to coerce the returned integer into floating point format at the assignment statement. This leads to a result that is quite wrong:
The square root of 2 is 1070596096.000000(The precise answer is machine dependent; but it will be wrong.) However, if the line
extern double sqrt();is placed before the assignment statement, the compiler will understand that the function sqrt() returns a double and will not do any type coercion at the assignment statement; the result in this case is
The square root of 2 is 1.414214Every extern declaration must have a corresponding definition (unless the variable or function in it is never referenced). Precisely what constitutes an acceptable definition varies among compilers.With some compilers, the single definition at the top level (that is, not contained inside any function's body) is taken to be the definition associated with extern declarations. If more than one such definition occurs, the compiler (actually, the linker) will report an error. These errors are of the form "variable (or function) multiply defined."
Other compilers follow the ANSI C standard and use a more complex scheme. They consider a top-level declaration of a variable to be a tentative definition if the storage class is static or omitted. If a tentative definition is found in which the variable is initialized, that is taken to be the definition and the other tentative definitions become declarations. Otherwise, the first tentative definition becomes the definition and the rest become declarations.
For example, suppose Listing 4 and Listing 5 are two separate source files belonging to the same program. If the first rule of defining variables is followed, each source file will compile correctly, but when they are linked, the linker will find two definitions of testcalled, and report that testcalled is multiply defined. If the ANSI rule of defining variables is used however, the statement
int testcalled;in test.c is considered a tentative definition, and the statement
int testcalled = 0;in main.c is considered the real definition, because testcalled is initialized. Hence, the tentative definition in test.c becomes a declaration and the linking procedure succeeds. Note that if the statement in test.c had been
int testcalled = 0;there would have been two definitions, not one, and the linker would have complained that testcalled had been multiply defined.The rules of global definition also apply to another storage class, which on first glance seems to be the most complicated.
Static
Variables with static storage class retain their value throughout the life of the program. When used outside a function definition, static has the side effect of not allowing the variable or function to be referenced anywhere except within the enclosing source file.The slightly reworked versions of main.c and test.c in Listing 6 and Listing 7 illustrate the effect of static on variable life.
When compiled, linked and executed, this program generates:
test() called, testcalled = 1 test() called, testcalled = 2If, for comparison, we omit the storage class keyword static from the declaration of testcalled in test(), testcalled will be an automatic variable. As an automatic, testcalled will be created each time test() is called, and discarded each time test() returns. Hence, we get the following result:
test() called, testcalled = 1 test() called, testcalled = 1This graphically points out that regardleas of their scope, static variables retain whatever value they are assigned throughout the life of the program, whereas automatic variables do not.When declared outside a function, the storage class static has one additional side effect: it limits the scope of the variable or function so declared to the file in which it is declared. For example, a program built from the main.c in Listing 4 and the test.c in Listing 8 would produce
test() called, testcalled = 0 test() called, testcalled = 0because the variable testcalled in test.c is not visible to any other file. So, it and the variable testcalled in main.c are completely different.To illustrate what happens when a function is declared static, I've combined main.c and test.c into one file, and made test() static (see Listing 9) . The result is:
test() called, testcalled = 1 test() called, testcalled = 2If, however, I put test() into a separate file (Listing 10) , both files main.c and test.c will compile, but the linker will complain that "test is undefined". Because test is declared as static, it is only visible in the file test.c; it cannot be accessed by anything in the file main.c.
An Example
Listing 11 contains a very simple program that sets a variable total to 20, and loops, adding 20 each time through, until total's value is more than 1000 or until 1000 loops have been made.First, note that the variable counter is declared automatic. It could just have easily been declared static; the effect would be the same, since it is local to main and exists until that routine exits (at which time the program exits.) So, counter could be any storage class except extern.
However, notice that total is defined as static on line 13A, thus total will increase in value each time dowork is called. The program would exit when total reached 1020. If, instead, the word static were replaced by auto (or register, or just omitted) then each time dowork was called, total would be recreated and reinitialized to 20. In this alternative version dowork would always return 40, and the program would loop 1000 times before exiting.
In yet another alternative, we could move total outside the function dowork and put it either above or below main. We could even put it after dowork, but then we would need the statement
extern int total;somewhere before line 14A (otherwise total would be undefined and undeclared at that point, causing a compile-time error.)If we split this example into two files main.c (Listing 12) and dowork.c (Listing 13) and declare the function dowork static, the program will not compile correctly because the function dowork isn't defined anywhere (so far as the function main is concerned).
In this variation line 13B could be moved before line 11B without changing anything. In fact, line 3B could be put before line 1B, and every occurrence of the variable counter could be renamed total without any problem, because the variable total in the file dowork.c is declared static and hence is defined for that file only.
Conclusion
By choosing storage classes carefully, a programmer can balance speed against memory used. Moreover, because static affects the visibility of objects, a C programmer who plans to practice sophisticated modular design, must thoroughly understand the ramifications of storage class.