Chapter 13: Storage
The introduction contrasts the lifetimes of previously encountered objects with storage instances created by recursive calls and dynamic allocation.
After this chapter you will be able to
- Syntactic object lifetimes. Explain how the program’s syntactic structure determines the lifetime and identifier visibility of variables and compound literals discussed here.
- Dynamically allocated storage. Describe what dynamic allocation initially creates and when that storage acquires a type.
- Dynamic storage allocation. Explain why programs use dynamic allocation and describe the roles of malloc, free, and the specialized allocation functions.
- Allocation failure checks. Explain what malloc returns when allocation fails and how that affects an operation using the returned pointer.
- Allocation size and pointer type. Explain why malloc sizes are expressed in bytes, how sizeof *pointer keeps an allocation matched to its intended element type, and why its return need not be cast.
- Initial state of allocated storage. Explain what state storage returned by malloc has before assignment and what assignment supplies to its elements.
- Circular buffer representation. Explain how the buffer uses tab, cap, start, and len to store elements, including when the occupied positions wrap around the end of the array.
- Buffer allocation and destruction. Describe how initialization allocates storage for tab, how allocation failure is detected, and how destruction releases that storage.
- Reallocation results and pointer validity. Explain what realloc returns on success or failure and why callers must use its returned pointer rather than assume the old pointer remains valid.
- Content preservation during resizing. Describe what content realloc preserves and why enlarging a wrapped circular buffer can require rearranging elements afterward.
- Allocation and deallocation balance. Explain why each dynamic allocation must be matched by a free and each free must correspond to an allocation, even when the calls occur in different functions. Describe why realloc can complicate a simple count.
- Valid pointers for free. Describe which pointers may be passed to free and distinguish them from pointers to other objects, already-freed allocations, or a smaller part of an allocation.
- Flexible array member structure. Describe how a flexible array member couples an array with metadata in a structure: it is an incomplete array declared as the structure’s last member, with a length member intended to describe its elements.
- Storage and length consistency. Explain why allocating a structure with a flexible array member requires enough storage for the whole structure and why the programmer must manually keep its length member consistent with the array.
- Visibility and object usability. Explain how an identifier’s scope and shadowing differ from the usability of the object it names.
- Object lifetimes and durations. Describe what an object’s lifetime means, name the four storage durations introduced here, and state the lifetime boundaries given for allocated storage.
- Static-duration objects and lifetime. Describe which objects the section identifies as having static storage duration and how long they live.
- Initialization of static-duration objects. Explain why objects with static storage duration are initialized from the start, including what happens without an explicit initializer and why an address initializer may need startup support.
- Automatic object lifetimes. Explain when most local automatic objects are created and destroyed, and why recursion can produce several instances of the same local object.
- Register objects and aliasing. Explain why register objects cannot alias and why the section recommends register for certain performance-critical locals but not arrays.
- Temporary return object lifetime. Describe why a function return value containing an array can give rise to a temporary object, and contrast its lifetime with a compound literal's.
- Ordinary automatic object lifetime. Explain that a non-VLA automatic object's lifetime begins when its definition's scope is entered, not when execution first reaches the definition, and ends when that scope is left. Describe why the example's use of *p before the definition is encountered is well defined.
- Repeated compound literal evaluation. Describe how revisiting the compound literal evaluates its initializer each time without creating a different compound-literal object on each visit. Relate that to the example's successive values and equal, valid pointers.
- VLA lifetime exception. Distinguish a VLA's lifetime from that of a non-VLA object: it starts when its definition is encountered and ends when its visibility scope is left. Explain why the example's jump-before-definition trick cannot be used with a VLA.
- Initialization by storage duration. Distinguish the default initialization of static and thread-storage objects from the explicit initialization required for automatic and allocated objects.
- Choosing an initialization method. Explain when to use initializers, calloc, or assignment to put an object in a well-defined state.
- Consistent initialization functions. Describe the proposed _init function convention and explain how it supports well-defined pointer initialization and reusable array initialization.
- Fixed-size automatic storage setup. Trace how the example function reserves space for its automatic objects and explain what that setup does not initialize.
- Optimized code versus objects. Explain how the optimized version can look unlike the unoptimized assembly while preserving the C program's effects.
What it builds on
- Compound literals (chapter 5) · Introduces compound literals.
- Recursion (chapter 7) · Introduces recursive functions.
- Effective types (chapter 12) · Discusses effective types.
- Pointers to unspecific objects (chapter 12) · Introduces pointers to unspecific objects.
- Declarations (chapter 2) · The distinction between declaring an identifier and using it.
- Definitions (chapter 2) · The distinction between declarations and definitions.
- Initializers (chapter 5) · Assumes familiarity with initializers.
- Array declaration (chapter 6) · The earlier treatment of array declarations.
Section by section
Introduction
The introduction contrasts the lifetimes of previously encountered objects with storage instances created by recursive calls and dynamic allocation.
- Syntactic object lifetimes: Explain how the program’s syntactic structure determines the lifetime and identifier visibility of variables and compound literals discussed here.
Why it matters: This distinction provides a starting point for discussing storage duration, lifetime, and visibility. - Recursive call storage instances: Explain why the section distinguishes a storage instance from an object when discussing variables in recursive functions.
Why it matters: The distinction makes it possible to talk about separate instances across recursive calls. - Dynamically allocated storage: Describe what dynamic allocation initially creates and when that storage acquires a type.
Why it matters: It distinguishes dynamically allocated storage from objects introduced with a declared type.
Key terms: object lifetime, identifier visibility, compound literals, static, storage instance, recursive function, object, dynamic allocation, storage instances, byte arrays
13.1 malloc and friends
Introduces dynamic allocation in C, the allocation functions, string-duplication helpers, and the rules for sizing, checking, and initializing allocated storage.
- Dynamic storage allocation: Explain why programs use dynamic allocation and describe the roles of malloc, free, and the specialized allocation functions.
Why it matters: Dynamic allocation accommodates data whose storage needs are not fixed in advance. - Allocation failure checks: Explain what malloc returns when allocation fails and how that affects an operation using the returned pointer.
Why it matters: A returned pointer must be checked before an operation that depends on allocated storage. - Bounded string duplication: Distinguish the input assumptions and returned-string behavior of strdup and strndup, and explain the stated preference when a bound is known.
Why it matters: A known bound makes strndup the preferable interface under the assumptions described here. - Allocation size and pointer type: Explain why malloc sizes are expressed in bytes, how sizeof *pointer keeps an allocation matched to its intended element type, and why its return need not be cast.
Why it matters: Deriving byte counts from the pointed-to type avoids a mismatch when that type changes, and an unnecessary cast can hide a declaration error. - Initial state of allocated storage: Explain what state storage returned by malloc has before assignment and what assignment supplies to its elements.
Why it matters: Obtaining storage does not by itself create initialized, typed elements ready to use as values.
Key terms: dynamic allocation, malloc, free, calloc, realloc, aligned_alloc, null pointer, failed allocations, strdup, strndup
13.1.1 A complete example with varying array size
A dynamically allocated circular buffer example shows how to manage its storage, resize it while preserving its contents, and guide callers toward safe use of its interface.
- Circular buffer representation: Explain how the buffer uses tab, cap, start, and len to store elements, including when the occupied positions wrap around the end of the array.
Why it matters: This representation explains both how elements are found and why resizing a wrapped buffer takes extra work. - Buffer allocation and destruction: Describe how initialization allocates storage for tab, how allocation failure is detected, and how destruction releases that storage.
Why it matters: The buffer must account for both successful allocation and allocation failure before it can safely hold values. - Reallocation results and pointer validity: Explain what realloc returns on success or failure and why callers must use its returned pointer rather than assume the old pointer remains valid.
Why it matters: Keeping or ignoring the wrong pointer after resizing can invalidate access to the buffer. - Content preservation during resizing: Describe what content realloc preserves and why enlarging a wrapped circular buffer can require rearranging elements afterward.
Why it matters: A resized allocation can retain every relevant value yet still need rearrangement to represent the same logical buffer. - Overlap-aware element copying: Distinguish why one wrapped-buffer enlargement case uses memcpy while the other requires memmove.
Why it matters: The choice of copy function depends on whether the source and destination might overlap. - Deprecated structure member access: Explain how the example permits user-declared circular objects while discouraging direct access to their members.
Why it matters: The interface allows local circular variables without encouraging callers to depend on the structure's internal fields.
Key terms: tab, cap, start, len, circular buffer, malloc, free, null pointer, realloc, [[nodiscard]]
13.1.2 Ensuring consistency of dynamic allocations
This subsection explains how to keep dynamic allocations consistent by matching allocations with frees and passing only valid pointers to free.
- Allocation and deallocation balance: Explain why each dynamic allocation must be matched by a free and each free must correspond to an allocation, even when the calls occur in different functions. Describe why realloc can complicate a simple count.
Why it matters: Checking that allocations and frees balance helps you catch leaks before they exhaust resources. - Valid pointers for free: Describe which pointers may be passed to free and distinguish them from pointers to other objects, already-freed allocations, or a smaller part of an allocation.
Why it matters: Passing an invalid pointer to free can corrupt a program's memory.
Key terms: allocation, free, memory leak, realloc, malloc, calloc, aligned_alloc, null
13.1.3 Flexible array members
This section introduces flexible array members as a way to keep an array and its length in one structure, while explaining the storage and consistency requirements they leave to the programmer.
- Flexible array member structure: Describe how a flexible array member couples an array with metadata in a structure: it is an incomplete array declared as the structure’s last member, with a length member intended to describe its elements.
Why it matters: The structure’s layout lets the array and the information describing it travel together. - Storage and length consistency: Explain why allocating a structure with a flexible array member requires enough storage for the whole structure and why the programmer must manually keep its length member consistent with the array.
Why it matters: Correct storage size and an accurate length are both the programmer’s responsibility.
Key terms: flexible array member, incomplete array, length, storage, length member, consistency
13.2 Storage duration, lifetime, and visibility
This section distinguishes an identifier’s visibility from an object’s identity and lifetime, then introduces storage durations and their relationship to linkage and compound literals.
- Visibility and object usability: Explain how an identifier’s scope and shadowing differ from the usability of the object it names.
Why it matters: A name becoming unavailable does not necessarily make its object unusable. - Definitions and object identity: Distinguish a variable definition that creates an object from an extern declaration that refers to an existing object.
Why it matters: Counting names or declarations alone does not tell you how many objects a program has. - Read-only literal overlap: Explain why distinct array definitions have distinct addresses while read-only object literals may share storage.
Why it matters: Equal literal contents do not guarantee a separate stored object for each occurrence. - Object lifetimes and durations: Describe what an object’s lifetime means, name the four storage durations introduced here, and state the lifetime boundaries given for allocated storage.
Why it matters: An object’s accessibility depends on execution time as well as where its name appears in the program. - Linkage and storage terminology: Distinguish linkage from storage duration and explain the cautions about the extern and static storage-class names.
Why it matters: Similar-sounding storage and linkage terms answer different questions about a program. - Compound literal lifetime rule: State the section’s rule for compound literal lifetime and its stated possible exception.
Why it matters: The usual lifetime rule has a specific qualification for read-only compound literals in block scope.
Key terms: visibility scope, identifier, shadowed, object, definition, declaration, extern, distinct object, read-only object literals, string literals
13.2.1 Static storage duration
This section identifies objects with static storage duration and explains their program-long lifetime, initialization, and visibility.
- Static-duration objects and lifetime: Describe which objects the section identifies as having static storage duration and how long they live.
Why it matters: Storage duration tells you when an object exists, not just where its declaration appears. - Initialization of static-duration objects: Explain why objects with static storage duration are initialized from the start, including what happens without an explicit initializer and why an address initializer may need startup support.
Why it matters: A static-duration object has an initial value even when its definition supplies no explicit initializer. - Lifetime versus name visibility: Distinguish an object's program-long lifetime from where its name is visible, using the B and extern examples.
Why it matters: An object's continued existence does not imply that every part of the program can refer to it by name.
Key terms: static storage duration, file scope, static, thread_local, string literals, lifetime, initialization, process startup procedure, linker, visible
13.2.2 Automatic storage duration
This section explains the lifetimes of ordinary automatic and temporary objects, and how register declarations affect address-taking and aliasing.
- Automatic object lifetimes: Explain when most local automatic objects are created and destroyed, and why recursion can produce several instances of the same local object.
Why it matters: Block lifetime and recursive instances determine which local object exists at a given point in execution. - Register objects and aliasing: Explain why register objects cannot alias and why the section recommends register for certain performance-critical locals but not arrays.
Why it matters: The restriction on address-taking explains both the potential optimization benefit and the limitation for arrays. - Temporary return object lifetime: Describe why a function return value containing an array can give rise to a temporary object, and contrast its lifetime with a compound literal's.
Why it matters: An addressable-looking member of a returned value does not remain available for the rest of the block.
Key terms: automatic storage duration, lifetime, block, recursive call, register, & operator, alias, arrays, temporary lifetime, function return value
13.3 Digression: Using objects before their definition
This section explains why an ordinary automatic object can be used before execution reaches its definition, how repeated evaluation affects its value, and why VLAs follow a different lifetime rule.
- Ordinary automatic object lifetime: Explain that a non-VLA automatic object's lifetime begins when its definition's scope is entered, not when execution first reaches the definition, and ends when that scope is left. Describe why the example's use of *p before the definition is encountered is well defined.
Why it matters: It explains why the example's apparently premature object access is valid. - Repeated compound literal evaluation: Describe how revisiting the compound literal evaluates its initializer each time without creating a different compound-literal object on each visit. Relate that to the example's successive values and equal, valid pointers.
Why it matters: It separates repeated initialization from the mistaken idea that each visit creates a new object. - VLA lifetime exception: Distinguish a VLA's lifetime from that of a non-VLA object: it starts when its definition is encountered and ends when its visibility scope is left. Explain why the example's jump-before-definition trick cannot be used with a VLA.
Why it matters: It prevents applying the ordinary automatic-object rule to arrays whose size is not available until runtime.
Key terms: lifetime, scope of definition, automatic objects, compound literal, initializer, p, q, VLA, visibility scope, runtime property
13.4 Initialization
This section explains how storage duration affects initialization and how consistent initialization functions can keep allocated objects in a well-defined state.
- Initialization by storage duration: Distinguish the default initialization of static and thread-storage objects from the explicit initialization required for automatic and allocated objects.
Why it matters: Storage duration tells you whether an object starts in a well-defined state without further action. - Choosing an initialization method: Explain when to use initializers, calloc, or assignment to put an object in a well-defined state.
Why it matters: The right method depends on whether zeroed bits actually provide valid initial values for the type. - Consistent initialization functions: Describe the proposed _init function convention and explain how it supports well-defined pointer initialization and reusable array initialization.
Why it matters: A predictable initialization interface makes allocation failures and repeated setup easier to handle without leaving pointer variables indeterminate. - Type-generic allocation macro: Explain how the described macro can select a type's initialization function and forward its initialization arguments.
Why it matters: It shows one way to reuse the initialization convention without writing a separate allocation wrapper for every type.
Key terms: static storage duration, thread-storage duration, automatic storage duration, allocated storage duration, default initialization, initializers, calloc, malloc, assignment, VLAs
13.5 Digression: A machine model
This optional digression uses assembly for one machine to illustrate how automatic storage is set up, how optimization can change the resulting code, and why VLAs need runtime stack adjustments.
- Machine and assembly model: Describe the machine model used for the example and explain why its assembly listing is specific to a particular architecture and toolchain.
Why it matters: It helps you read the listing as an example of an implementation, not as rules every C program must follow. - Fixed-size automatic storage setup: Trace how the example function reserves space for its automatic objects and explain what that setup does not initialize.
Why it matters: The assembly example makes the distinction between an object's available storage and its initialization concrete. - Optimized code versus objects: Explain how the optimized version can look unlike the unoptimized assembly while preserving the C program's effects.
Why it matters: A direct stack layout is useful for illustration, but it is not a promise about optimized machine code. - Runtime storage for VLAs: Distinguish the stack adjustment needed for a VLA from the fixed-size adjustment in the example.
Why it matters: It shows the limit of the simple, fixed-size stack setup used earlier in the example.
Key terms: von Neumann model, assembler, registers, instructions, automatic objects, stack, %rbp, %rsp, initialization, optimization
13.6 Summary
Where this is used later
- Text processing (chapter 14) · Dynamic allocation with malloc and realloc.
- Storage exhaustion (chapter 15) · Storage exhaustion can result from leaked allocations.
- (chapter intro) (chapter 16) · The earlier discussion of register.
- (chapter intro) (chapter 19) · Assumes the idea of an object's lifetime.
- Race-free initialization and destruction (chapter 20) · Storage duration and object lifetime.
- Thread-local data (chapter 20) · Returns to data associated with threads.
- Inline functions (chapter 16) · Static storage duration and static objects.
- Access violations (chapter 15) · Understanding when an object ceases to exist helps identify invalid accesses.
Concepts explained in this chapter
- automatic storageAutomatic storage duration is a C storage duration in which an object's storage is created on entry to a relevant block or function and released when execution leaves it.
- lifetimeAn object's lifetime is the interval of program execution during which the object exists and can be accessed according to the language rules.
- mallocmalloc is a C library function that allocates a specified number of bytes of uninitialized storage and returns a pointer to it or a null pointer on failure.
- reallocrealloc is a C library function that changes the size of an allocated storage region while preserving its existing contents up to the applicable size.
- storage durationStorage duration is the lifetime category that determines when a C object is created, exists, and is initialized or destroyed.
- automatic objectAn automatic variable is a C object with automatic storage duration, normally created on entry to a block and destroyed on exit from it.
- static storageA static object is a C object with static storage duration whose lifetime extends across the entire program execution.
- allocated storage durationAllocated storage duration is the duration of an object created by a dynamic allocation function and ended by deallocation, reallocation, or program termination.
- thread storage durationThread storage duration is an object storage duration associated with a particular thread of execution.
- storage classA storage class specifier is a C declaration specifier that controls an object's storage duration, linkage, or related storage properties.
- circular bufferA circular buffer is a fixed-capacity queue-like data structure whose logical sequence wraps around the end of an underlying array.
- memmovememmove is a C standard-library function that copies bytes between memory regions while correctly handling overlap between the regions.
- nodiscard attributeThe nodiscard attribute is a C23 attribute that recommends issuing a diagnostic when a function return value is ignored.
- vectorA dynamic array is an array-like data structure whose storage can be allocated or resized during program execution.
- calloccalloc is a C standard-library function that allocates storage for an array of elements and initializes all allocated bytes to zero.
- memory allocationDynamic allocation is a runtime mechanism that obtains storage for objects independently of ordinary declarations and releases it according to allocation operations.
- base pointerA base pointer is a processor register used as a stable reference point for accessing a function's stack-resident data.
- hardware registerA hardware register is a small, processor-resident storage location that holds values used during instruction execution.
- stackA stack is a last-in, first-out storage structure in which function-call contexts are added and removed as calls and returns occur.
- stack pointerA stack pointer is a processor register that identifies the current top of a function-call stack area.
- von Neumann modelThe von Neumann model is a computer architecture model with a processor, registers, linearly addressable memory containing programs and data, and a finite instruction set.
- external linkageExternal linkage is the property allowing declarations in different translation units to designate the same C entity.
- internal linkageInternal linkage is the property restricting an identifier's designated entity to a single translation unit.
- linkageLinkage is the C property determining whether declarations in different scopes or translation units can designate the same entity.
- linkerA linker is a tool that combines compiled program components and resolves references to produce an executable or other linked program image.
- public visibilityVisibility is the property determining whether an identifier can be referenced from a particular part of a program.
- freefree is a C library function that releases dynamically allocated storage previously obtained from a compatible allocation function.
- effective typeAn effective type is the type that C associates with storage allocated without a declared type through the accesses or stores that establish how the storage may be used.
- strdupstrdup is a C library function that allocates storage and copies a null-terminated string into it.
- strndupstrndup is a C23 library function that allocates storage and copies at most a specified number of characters from a string.
- initializerAn initializer is C syntax that supplies an initial value for an object declared in source code.
- initializationInitialization is the process of providing an initial value for a C object as part of its declaration or creation.
- gotogoto is a C jump statement that transfers execution to a labeled statement within the same function.
- variable-length arrayA variable-length array is a C array whose bound is determined at run time rather than being an integer constant expression.
- register keywordregister is a C storage-class specifier that requests automatic storage for an object and permits implementation-specific restrictions on taking its address.
- temporary objectA temporary object is an unnamed C object created for an intermediate expression or initialization and given automatic temporary lifetime.
- storage instanceA storage instance is a particular occurrence of storage associated with an object declaration or function invocation, especially when recursive calls create separate occurrences.
- missing valuenullptr is the C23 null pointer constant used to represent a null pointer value.
- __VA_ARGS____VA_ARGS__ is a predefined C preprocessor identifier that expands to the arguments supplied for a variadic macro's ellipsis parameter.
- token concatenationThe token concatenation operator ## joins two preprocessing tokens into a single preprocessing token during macro replacement.
These guides are generated from the text of Manning books by Manning's knowledge graph and link to the sections they draw on. The full books are available on liveBook and at manning.com.
