In `main`, a local `unsigned i` can hide a file-scope `unsigned i`. An inner `extern unsigned i` names the file-scope object again; it does not create a third object. The example separates two questions: which identifier is visible here, and how long does its object exist? 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 */
}
}
Follow the three declarations of `i`: the local definition creates a distinct object, while the inner `extern` declaration refers to the file-scope object and prints `1`. section 13.2
Storage duration describes when an object's storage exists. A file-scope object has static storage duration and lasts for the whole execution. Calls to a function, including recursive calls, have separate instances of their local variables. Dynamically allocated storage requires explicit tracking and can be freed. Do not infer an object's duration from whether its name is currently visible: a pointer can still designate an object whose identifier is shadowed. section 13.0 section 13.2 section 13.6
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 |
Use the table to compare declarations by storage class, scope, linkage, duration, and lifetime rather than treating those properties as interchangeable. section 13.2
TAKEAWAY: Storage duration affects initialization. Static and thread-storage-duration objects receive default initialization; automatic and allocated objects need an explicit initialization strategy. An initializer can establish a variable's state when it becomes visible. For dynamically allocated data, a type-specific initialization function can keep allocation and initialization together; `calloc` is suitable only when an all-bits-zero representation is a valid initialization for the data involved. section 13.4

