An Untold Story of Storage Class in C Programming Language

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:

Keyword Storage Duration Linkage Remarks
static Static Internal Sets internal linkage for objects at file scope; sets static storage duration for objects at block scope.
extern Static External 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.
auto Automatic Irrelevant Implied and therefore redundant for objects declared at block scope.
register Automatic Irrelevant 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.
typedef Irrelevant Irrelevant 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.
_Thread_local Thread Internal/external Introduced in C11, to represent thread storage duration. If used at block scope, it shall also include extern or static.

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 (int, unsigned, short, etc.) and top-level type qualifiers (const, 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 (void, char, int, signed long, unsigned long long, long double...).

Not all storage class specifiers are legal at a certain scope:

/* legal at block scope, illegal at file scope */
register int x;  
auto int y;

/* legal at both file and block scope */
static int z;  
extern int a;

/* legal and redundant at file scope, illegal at block scope */
extern int b = 5; 

/* legal because typedef is treated like a storage class specifier syntactically */
int typedef new_type_name;  

Storage Duration

Storage duration can be either static or automatic. For a declared object, it is determined depending on its scope and the storage class specifiers.

Static Storage Duration

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.

Thread Storage Duration

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 _Thread_local.

Variables with thread storage duration can be declared at both file scope and block scope. If declared at block scope, it shall also use static or 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.

Automatic Storage Duration

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.

External and Internal Linkage

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.

Read full tutorial on Storage Classes here.