Linkage determines whether declarations in different scopes or translation units can designate the same entity. External linkage lets identifiers in separate object files refer to one object or function; internal linkage keeps an identifier within one translation unit; identifiers without linkage do not connect declarations across scopes or files. The linker establishes correspondence for external-linkage identifiers. section 13.2
Listing 13.2 An example of shadowing with an extern variable
#include <stdio.h> 1
unsigned i = 1;
int main(void) { 2
unsigned i = 2; /* A new object */
if (i) { 3
extern unsigned i; /* An existing object */
printf("%u\n", i); /* prints 1 */
} else {
printf("%u\n", i); /* prints 2 */
}
}
This example contrasts a block-scope declaration that creates a new object with an extern declaration that refers to the existing file-scope object: it prints 1 inside the conditional and 2 in the alternative branch. section 13.2
Keep these distinctions explicit:
- Treat extern as a declaration mechanism, not as a guarantee of external linkage; it can refer to identifiers with either external or internal linkage. section 13.2
- Use static at file or block scope to enforce internal linkage for a variable or function; do not confuse that keyword with static storage duration. section 13.2
- Separate linkage from storage duration: static, automatic, allocated, and thread storage durations describe object lifetime rather than declaration correspondence. section 13.2
Table 13.1 Storage classes, scope, linkage of identifiers, and storage duration of the associated objects. Tentative indicates that a definition is implied only if there is no other definition with an initializer. Induced indicates that the linkage is internal if another declaration with internal linkage has been met prior to that declaration; otherwise, it is external. Compound literals follow similar rules with respect to the block in which they occur, if any.
| Class | Scope | Definition | Linkage | Duration | Lifetime |
| Initialized | File | Yes | External | Static | Whole execution |
extern, initialized |
File | Yes | External | Static | Whole execution |
| String literal | Any | Yes | N/A | Static | Whole execution |
static, initialized |
Any | Yes | Internal | Static | Whole execution |
constexpr |
Any | Yes | Internal | Static | Whole execution |
| Uninitialized | File | Tentative | External | Static | Whole execution |
extern, uninitialized |
Any | No | Induced | Static | Whole execution |
static, uninitialized |
Any | Tentative | Internal | Static | Whole execution |
thread_local |
File | Yes | External | Thread | Whole thread |
extern thread_local |
Any | No | External | Thread | Whole thread |
static thread_local |
Any | Yes | internal | Thread | Whole thread |
| Non-VLA | None | ||||
Non-VLA, auto |
Block | Yes | None | Automatic | Block of definition |
register |
None | ||||
| VLA | Block | Yes | None | Automatic | From definition to |
| end of block | |||||
Function return |
Block | Yes | None | Automatic | To the end of |
| with array | expression |
The table compares scope, definition status, linkage, storage duration, lifetime, and initialization across storage classes and related declarations. section 13.2

