Page 1 of 3 Static storage is boring - or is it? Find out in this extract from my latest book Deep C Dives.
Deep C Dives Adventures in C
By Mike James

Buy from Amazon.
Contents
Preface Prolog C Dive
- All You Need Are Bits
- These aren’t the types you’re looking for
- Type Casting
- Expressions
- Bits and More Bits
Extract: Bits!
- The Brilliant But Evil for
- Into the Void
- Blocks, Stacks and Locals
- Static Storage ***NEW!
- Pointers
- The Array and Pointer Arithmetic
- Heap, The Third Memory Allocation
- First Class Functions
Extract: First Class Functions
- Structs and Objects
- The Union
- Undefined Behavior
- Exceptions and the Long Jump
<ASIN:B0D6LZZQ8R>
Dive 9
Static Storage
“Up until the 1920s, everyone thought the universe was essentially static and unchanging in time.”
Stephen Hawking
We all know what a variable is, but often we don’t. The simplest mental model of a variable is a memory location associated with a name – a label. You can think of the variable as a box that you can put a bit pattern into and retrieve by name.
Consider a simple assignment:
int a = 42;
You can think of this as creating a box called a and storing the bit pattern for 42 in it. Similarly, using a in an expression retrieves the bit pattern from the box. Of course, in practice the box is a memory location and the label is the location’s address.
Sometimes it is important to know where the memory location is and how it is allocated. C supports two well-known ways of allocating memory. We have already met automatic variables, see Dive 8, which are allocated on the stack and are ideal for variables that have a block lifetime. Variables that are explicitly allocated on the heap, see Dive 11, are ideal for situations when you need the lifetime to be independent of any block. This is the stack versus heap distinction, but there are other ways that variables are implemented.
One of the most important is strangely named “file level storage” also known as “static storage”.
File Level Storage
To understand how file-level storage works, we have to consider how a program is compiled and run. The compiler reads the C code and for each C instruction it outputs a number of lower-level machine code instructions. These instructions are simply bit patterns and only differ from data in that they are examples of machine code instructions and the processor can read and obey them.
That is, a program is just data you can execute.
The output of the compiler is a binary file and to run the program the loader simply loads the file into the correct location in memory and then makes the processor jump to the address of the first instruction.
The fact that the program file is just a binary file that is loaded into memory means that it is possible to include data within it for the program to work with. That is, memory locations within the program can be used to store data. Usually the compiler arranges to store all of the data together in one area of the file and so we have the idea of a data block and a code block. Depending on the system and the compiler there can be multiples of both types of block in a single binary file.
File-level variables are stored in the binary file that the compiler generates and are stored within the program. Thus they are neither automatic variables stored on the stack nor are they heap variables. A file-level variable is defined by being declared outside of a block, i.e. outside of a function. This means that the declaration is at the top level of the file of code and hence a “file-level variable”. For example:
#include <stdio.h>
#include <stdlib.h>
int a = 42;
int main(int argc, char **argv)
{
int b = 314159;
printf("%d,%d", a, b);
return 0;
}
The variable a is declared at the file level and variable b is an automatic local variable. Recall that in C the main “program” is just another function and variables declared within it are automatic and stored on the stack. Variable a is compiled into the data area of the program and isn’t allocated space on the stack or the heap – it is part of the program file. It is also initialized when the program file is loaded. As it is part of the program, a has the maximum lifetime – it lives until the program terminates and the memory it occupied is reused by another program. This is very similar to the variable b, which lives for as long as the main function is active. In a single-threaded program life and main function life are the same as the program ends when the main function ends. In a multi-threaded program this isn’t necessarily true.
A big difference between the two variables is that a is accessible, i.e. in scope, everywhere in the file. That is, all of the code in the same file can access a, whereas variable b can only be accessed from main even though it has the same lifetime as a.
For example:
#include <stdio.h>
#include <stdlib.h>
int a = 42;
void myFunction(){
printf("%d,%d\n", a, b);
}
int main(int argc, char **argv)
{
int b = 314159;
printf("%d,%d\n", a, b);
myFunction();
return 0;
}
This works and myFunction prints the value of a, but it will not print the value of b, even if you try to.
Another difference is the way file-level variables are initialized. Consider the instruction:
int a = 42;
In this case, as it is a file-level variable, it is allocated in the program file and that data is set to 42 in the file. When the program is loaded into memory the variable already has 42 stored in it and no further action is taken. Compare this to:
int b = 314159;
In this case the variable is an auto and so it is allocated on the stack when the function, main, is called, this means that the stack then has to be set to 314159 as the program runs.
You can see that you get the initialization of the file-level variable for free as part of loading the program. This sometimes is incorrectly interpreted as file-level variables are more efficient, but this isn’t the case as loading from a file is slower than allocating and initializing variables.
|