Chapter 12: The C memory model
Introduces the C memory model as an abstraction for accessing objects through pointers, while establishing the roles of pointer types and byte-based object representations.
After this chapter you will be able to
- Pointer access and location abstraction. Explain that C pointers let a program address and access objects without identifying where those objects physically reside, and that modern operating systems typically provide this through virtual memory.
- Byte-based object representation. Describe the simplifying assumption the C memory model makes about how an object is represented.
- Objects as byte arrays. Explain that every C object, despite having a type, can be viewed at a lower level as an array of sizeof A unsigned char elements.
- Sizeof meaning and syntax. Describe what sizeof measures and distinguish its permitted syntax for an object expression from its required syntax for a type.
- Union views of object bytes. Explain how a union lets the example inspect an unsigned value as an array of unsigned char without making the union a collection of separate objects.
- Representation digits and byte order. Describe how the section treats unsigned-char bytes as representation digits, and distinguish little-endian from big-endian storage order.
- Pointer access and program state. Explain why a function given an object’s address can make its value after the call uncertain when the function’s definition is unavailable.
- Aliasing and pointer types. Describe how two pointers can access the same object, how that changes the two-argument example, and the type-based aliasing restriction stated here.
- Void pointer conversion and identity. Explain that any object pointer can convert to void* and back, and that converting back to the same pointer type preserves the pointer value. Distinguish object pointers from function pointers in this guarantee.
- Implicit data pointer conversions. Describe the two permitted forms of implicit data-pointer conversion and explain how conversion through void* can conceal a pointer-type mistake.
- Cast risks and exception. Justify the section’s advice to avoid casts, and describe its exception for inspecting an object’s representation byte by byte.
- Effective type access rule. Explain that an object must be accessed through its effective type or through a pointer to a character type, and that a variable or compound literal has the effective type given by its declaration.
- Alignment and pointer conversion. Explain what type alignment means and why converting a character pointer to an object pointer can be unsafe when the address is not suitably aligned.
What it builds on
- Address-of and object-of operators (chapter 11) · Using the address-of operator to obtain a pointer to an object.
- Pointers as opaque types (chapter 6) · The introduction of pointers as types.
- Arrays (chapter 6) · The reader has encountered arrays as a way to describe a sequence of elements.
- Strings are special (chapter 6) · The reader has encountered char in the context of strings.
- Array length (chapter 6) · The reader has previously encountered sizeof in the context of arrays.
- Structures (chapter 6) · The distinction from a struct assumes familiarity with structures.
- Unsigned integers (chapter 5) · The discussion assumes familiarity with unsigned integers and their representation.
- The abstract state machine (chapter 5) · The abstract state machine and its state.
Section by section
Introduction
Introduces the C memory model as an abstraction for accessing objects through pointers, while establishing the roles of pointer types and byte-based object representations.
- Pointer access and location abstraction: Explain that C pointers let a program address and access objects without identifying where those objects physically reside, and that modern operating systems typically provide this through virtual memory.
Why it matters: It explains why pointer-based code can access objects without depending on their physical location. - Pointer types and base types: State how a pointer type relates to its base type and distinguish pointer types whose base types differ.
Why it matters: The type of the addressed object remains significant even though its physical location is abstracted away. - Byte-based object representation: Describe the simplifying assumption the C memory model makes about how an object is represented.
Why it matters: This byte-based view is the starting point for discussing object representations.
Key terms: C memory model, address, virtual memory, pointer type, base type, object representation, bytes
12.1 A uniform memory model
This section explains how C accounts for every object as bytes, how character types relate to those bytes, and how sizeof reports an object's size.
- Objects as byte arrays: Explain that every C object, despite having a type, can be viewed at a lower level as an array of sizeof A unsigned char elements.
Why it matters: This gives you a common way to describe the storage occupied by otherwise different object types. - Character types and their roles: Distinguish the section's uses of char and unsigned char, and state what it says about the size of the three character types.
Why it matters: The same family of types appears in both text handling and the byte-level description of objects, but those are different uses. - Sizeof meaning and syntax: Describe what sizeof measures and distinguish its permitted syntax for an object expression from its required syntax for a type.
Why it matters: sizeof connects the byte-level model to a size you can express for either a particular object or a type.
Key terms: object, byte, unsigned char, unsigned char[sizeof A], char, signed char, character and string data, sizeof operator, sizeof(T), bytes
12.2 Unions
The section uses a union to inspect an unsigned integer’s bytes and explains how their order and size depend on the implementation.
- Union views of object bytes: Explain how a union lets the example inspect an unsigned value as an array of unsigned char without making the union a collection of separate objects.
Why it matters: The two views make an object’s byte representation available for inspection. - Representation digits and byte order: Describe how the section treats unsigned-char bytes as representation digits, and distinguish little-endian from big-endian storage order.
Why it matters: The same value need not have its bytes in the same order on every platform. - Platform-dependent character width: State which character-size assumptions made the example’s representation digits convenient to print in hexadecimal, and why those assumptions are not universal.
Why it matters: It keeps the example’s neat two-hex-digit output from being mistaken for a guarantee on every platform.
Key terms: union, struct, twofold.val, twofold.bytes, object representation, representation digits, UCHAR_MAX, endianness, little-endian, big-endian
12.3 Memory and state
This section explains how pointer access complicates the program’s abstract state, how aliasing affects optimization, and why avoiding unnecessary address-taking helps.
- Pointer access and program state: Explain why a function given an object’s address can make its value after the call uncertain when the function’s definition is unavailable.
Why it matters: Passing an address can prevent the caller from assuming an object still has its old value. - Aliasing and pointer types: Describe how two pointers can access the same object, how that changes the two-argument example, and the type-based aliasing restriction stated here.
Why it matters: Whether pointers can name the same object affects which values the compiler can safely treat as unchanged. - Avoiding unnecessary address taking: Justify the advice to avoid the & operator when an object’s address is not needed.
Why it matters: Keeping an address private can make it easier for the compiler to reason about a variable.
Key terms: abstract state machine, state, & operator, pointers, aliasing, base type, character types, optimization, alias
12.4 Pointers to unspecific objects
This section explains how void* provides a type-unspecific pointer to an object while preserving its address, and why losing type information calls for caution.
- Void pointer conversion and identity: Explain that any object pointer can convert to void* and back, and that converting back to the same pointer type preserves the pointer value. Distinguish object pointers from function pointers in this guarantee.
Why it matters: It lets you reason about what a conversion to void* loses—and what it preserves. - Costs of type erasure: Justify the section's advice to avoid void* when possible and explain why void itself is not useful for declaring a variable.
Why it matters: The advice helps you retain useful type information when working with object addresses.
Key terms: object pointer, void*, function pointer, identity operation, void, type information
12.5 Explicit conversions
This section explains why pointer conversions require care, why casts can hide type errors, and when a cast is useful for inspecting an object’s bytes.
- Narrow integer type uses: Explain why narrow integer types are mainly useful for large collections of small values, characters and strings, or inspecting an object’s bytes rather than for arithmetic.
Why it matters: It helps explain why unsigned char appears in this section’s byte-inspection example. - Implicit data pointer conversions: Describe the two permitted forms of implicit data-pointer conversion and explain how conversion through void* can conceal a pointer-type mistake.
Why it matters: A conversion through void* can make a dangerous type mismatch harder for the compiler to catch. - Cast risks and exception: Justify the section’s advice to avoid casts, and describe its exception for inspecting an object’s representation byte by byte.
Why it matters: Avoiding unnecessary casts preserves compiler help while leaving a way to inspect object bytes when needed.
Key terms: narrow integer types, int, char, unsigned char, data pointers, void*, qualifier, implicit conversions, cast, unsigned char*
12.6 Effective types
This section explains how effective types restrict access to C objects, including the special cases of unions and character types, and why the restriction helps compilers reason about aliasing.
- Effective type access rule: Explain that an object must be accessed through its effective type or through a pointer to a character type, and that a variable or compound literal has the effective type given by its declaration.
Why it matters: The declared type determines which accesses to these objects C permits. - Union member access exception: Describe why an object with an effective union type may be accessed through any of its members, subject to the bytes representing a valid value of the member’s access type.
Why it matters: This explains why the rule for ordinary declared objects does not prohibit the union member accesses described here. - One-way character access: Distinguish viewing an object through a character-type pointer from accessing an array of unsigned char through an unrelated type.
Why it matters: The character-type exception works in one direction, not as general permission to reinterpret a byte array. - Effective types and aliasing: Explain how effective-type restrictions let a compiler conclude, in the section’s example without a union, that a double cannot be accessed through a size_t pointer and thus the objects are different.
Why it matters: The access restriction also gives the compiler grounds to reason about whether two expressions refer to the same object.
Key terms: effective type, variable, compound literal, character type, union, effective union type, member, byte representation, unsigned char, array of unsigned chars
12.7 Alignment
This section explains why object alignment matters when converting character pointers and introduces C's alignment operators and their limits.
- Alignment and pointer conversion: Explain what type alignment means and why converting a character pointer to an object pointer can be unsafe when the address is not suitably aligned.
Why it matters: A cast may compile while the resulting access still fails because of alignment. - Alignment operators and limits: Describe what alignof reports and what alignas can request, and explain why neither makes an effective-type violation valid.
Why it matters: Specifying an address's alignment addresses one constraint on access, not every rule governing that access.
Key terms: alignment, word boundary, pointer cast, bus error, alignof, alignas, effective type rule, _Alignof, _Alignas
12.8 Summary
Where this is used later
- Using restrict qualifiers (chapter 16) · The question of whether two pointers may point to the same object.
- Storage duration, lifetime, and visibility (chapter 13) · Examines object properties that influence address-taking and aliasing decisions.
- malloc and friends (chapter 13) · Introduces pointers to unspecific objects.
- void pointers (chapter 18) · Pointers to unspecific objects
- Invalid conversions (chapter 15) · Conversions between pointer types and other types.
- (chapter intro) (chapter 13) · Discusses effective types.
- Type violations (chapter 15) · Rules for accessing objects through a different type
- (chapter intro) (chapter 16) · The earlier discussion of alignas and alignment.
Concepts explained in this chapter
- referenceA pointer type is a C type whose values designate objects or functions in storage.
- consistency modelThe C memory model is the language abstraction that defines how objects, storage, pointers, and program state relate without requiring a particular physical-memory arrangement.
- data conversionA cast is an explicit C conversion construct that requests conversion of an expression to a specified type.
- basic typeA base type is the type from which a derived C type, such as a pointer type, is formed.
- bytesA byte is the C memory unit corresponding to the storage occupied by one char object and used as the basic unit of object representation.
- union typeA C union is a user-defined type whose members occupy overlapping storage and are represented by the same memory location.
- alignmentAlignment is the implementation-defined requirement that an object or structure member begin at an address suitable for its type.
- 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.
- in-memory formatAn object representation is a model describing the physical or storage-level encoding of a value in memory.
- 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.
- endiannessEndianness is the ordering convention used to encode the bytes of multi-byte values in a representation.
- unsigned integerunsigned int is a C unsigned integer type that represents nonnegative whole numbers within an implementation-defined range.
- address-of operatorThe address-of operator is the C unary operator that obtains a pointer to an object or function designated by its operand.
- aliasingAliasing is the situation in which multiple pointer expressions or access paths designate the same object or overlapping storage.
- stateHidden state is implementation or library state that affects program behavior without being directly represented by ordinary program values.
- void pointerA void pointer is a C pointer to void that can hold the address of an object of any object type, subject to valid conversions and qualification rules.
- char(n)char is a C character type that occupies exactly one byte and is intended to represent individual characters.
- sizeof operatorThe sizeof operator is a C unary operator that yields the size in bytes of a type or object representation.
- stdbit.hstdbit.h is the C23 standard header that provides type-generic macros for inspecting and manipulating bits in unsigned integer values.
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.
