A storage class is used to set the scope of a variable or function. By knowing the storage class of a variable, we can determine the life-time of that variable during the run-time of the program.
[auto|register|static|extern] <Data type> <Variable name>[ = <Value>];
[static _Thread_local|extern _Thread_local|_Thread_local] <Data type> <Variable name>[ = <Value>]; /* since >=C11 */
typedef int foo;
extern int foo;
Storage class specifiers are the keywords which can appear next to the top-level type of a declaration. The use of these keywords affects the storage duration and linkage of the declared object, depending on whether it is declared at file scope or at block scope:
|Sets internal linkage for objects at file scope; sets static storage duration for objects at block scope.
|Implied and therefore redundant for objects defined at file scope which also have an initializer. When used in a declaration at file scope without an initializer, hints that the definition is to be found in another translation unit and will be resolved at link-time.
|Implied and therefore redundant for objects declared at block scope.
|Relevant only to objects with automatic storage duration. Provides a hint that the variable should be stored in a register. An imposed constraint is that one cannot use the unary
& "address of" operator on such an object, and therefore the object cannot be aliased.
|Not a storage class specifier in practice, but works like one from a syntactic point of view. The only difference is that the declared identifier is a type, rather than an object.
|Introduced in C11, to represent thread storage duration. If used at block scope, it shall also include
Every object has an associated storage duration (regardless of scope) and linkage (relevant to declarations at file scope only), even when these keywords are omitted.
The ordering of storage class specifiers with respect to top-level type specifiers (
short, etc.) and top-level type qualifiers (
volatile) is not enforced, so both of these declarations are valid:
int static const unsigned a = 5; /* bad practice */
static const unsigned int b = 5; /* good practice */
It is, however, considered a good practice to put storage class specifiers first, then any type qualifiers, then the type specifier (
unsigned long long,
Not all storage class specifiers are legal at a certain scope:
register int x; /* legal at block scope, illegal at file scope */
auto int y; /* same */
static int z; /* legal at both file and block scope */
extern int a; /* same */
extern int b = 5; /* legal and redundant at file scope, illegal at block scope */
/* legal because typedef is treated like a storage class specifier syntactically */
int typedef new_type_name;
Storage duration can be either static or automatic. For a declared object, it is determined depending on its scope and the storage class specifiers.
Variables with static storage duration live throughout the whole execution of the program and can be declared both at file scope (with or without
static) and at block scope (by putting
static explicitly). They are usually allocated and initialized by the operating system at program startup and reclaimed when the process terminates. In practice, executable formats have dedicated sections for such variables (
rodata) and these whole sections from the file are mapped into memory at certain ranges.
This storage duration was introduced in C11. This wasn't available in earlier C standards. Some compilers provide a non-standard extension with similar semantics. For example, gcc supports
__thread specifier which can be used in earlier C standards which didn't have
Variables with thread storage duration can be declared at both file scope and block scope. If declared at block scope, it shall also use
extern storage specifier. Its lifetime is the entire execution the thread in which it's created. This is the only storage specifier that can appear alongside another storage specifier.
Variables with automatic storage duration can only be declared at block scope (directly within a function or within a block in that function). They are usable only in the period between entering and leaving the function or block. Once the variable goes out of scope (either by returning from the function or by leaving the block), its storage is automatically deallocated. Any further references to the same variable from pointers are invalid and lead to undefined behaviour.
In typical implementations, automatic variables are located at certain offsets in the stack frame of a function or in registers.
Linkage is only relevant to objects (functions and variables) declared at file scope and affects their visibility across different translation units. Objects with external linkage are visible in every other translation unit (provided that the appropriate declaration is included). Objects with internal linkage are not exposed to other translation units and can only be used in the translation unit where they are defined.