Every variable has a storage class that determines its scope and lifetime. These storage classes include the following: auto, register, extern, and static. Each of these classes is also a keyword that can be placed before the data type to determine to which storage class a variable belongs.
Auto
The default storage class for local variables is auto, which can be explicitly specified with the
auto keyword. Memory for automatic variables is allocated when the code block is entered and freed upon exit. The scope of these variables is local to the block in which they are declared, as well as any nested blocks.
int main(void) {
auto int localVar; /* auto variable */
}
Register
The
register storage class hints to the compiler that a local variable will be heavily used and should therefore be kept in a CPU register instead of RAM memory to provide quicker access. Variables of the register storage class cannot use the address-of operator (
&), since registers do not have memory addresses. They also cannot be larger than the register size, which is usually the same as the processor’s word size.
int main(void) {
register int counter; /* register variable */
}
Use of the register keyword has become deprecated since modern compilers are automatically able to optimize which variables should be stored in registers.
External
The
external storage class, specified with the
extern keyword, is used to reference a variable or function defined in another compilation unit. A compilation unit consists of a source file plus any included header files. Functions default to the external storage class, so marking function prototypes with
extern is optional.
/* app.c */
extern void foo(void); /* declared function */
int main(void) {
foo(); /* external function call */
}
/* func.c */
void foo(void) {} /* defined function */
When
extern is used with a global variable it becomes declared but not defined, so no memory is allocated for it. This tells the compiler that the variable is defined elsewhere. As with functions, it is necessary to declare global variables before they can be used in a compilation unit outside the one containing the definition.
/* app.c */
int globalVar; /* defined variable */
int main(void) {
globalVar = 1;
}
/* func.c */
extern int globalVar; /* declared variable */
int foo(void) {
globalVar++;
}
Keep in mind that a global variable or function may be declared externally multiple times in a program, but they may only be defined once.
Static
The static storage class restricts the scope of a global variable or function to the compilation unit that defines it. The lifetime of static entities is the whole program duration, which is the same as entities belonging to the external storage class.
/* Only visible within this compilation unit */
static int myInt;
static void myFunc(void) {}
Local variables may be declared as static to make the function preserve the variable for the duration of the program. A static local variable is only initialized once—when execution first reaches the declaration—and that declaration is then ignored every subsequent time the execution passes through.
/* Store number of calls to this function */
void myFunc(void) {
static int count = 0;
count++;
}
Knowing that a code entity can only be accessed and altered within a limited scope simplifies debugging, as it reduces potential dependencies between compilation units. Therefore, it is a good idea to declare all global variables and functions as static, unless they have an actual need to be exposed outside of their own compilation unit.
Volatile
Another type modifier in C is
volatile. This
modifier tells the compiler that a variable’s value may be changed by something external to the program and that the value must therefore be reread from memory every time it is accessed. Like
const, the
volatile modifier can appear either before or after the type, and it can be used together with a storage class modifier.
volatile int var; /* recommended order */
int volatile var; /* alternative order */
In the following example, the function waits for a variable to be set by some external event. Without the
volatile modifier, the compiler may decide to optimize this loop condition by replacing it with an infinite loop, as it assumes the variable is never changed.
void poll(void) {
while(ext == 0) {}
}
Global variables should be declared volatile if their value is shared and can be changed externally. This can occur because an interrupt service routine modifies the variable, or because it is changed by another thread in a multi-threaded application. A third use case for volatile is with memory-mapped peripheral devices, which can change hardware registers outside of the program’s control.