Chapter 12: The C Memory Model
Level 2 — Cognition
Objects, bytes, unions, effective types, alignment -- the deepest C internals.
This is the most technically dense chapter in the book. The C memory model defines how data is stored, accessed, and interpreted at the byte level. Understanding it is essential for writing correct low-level C and for appreciating what Auto abstracts away.
12.1 Uniform Memory Model
In C, all data -- integers, floats, structs, arrays -- is stored as a contiguous sequence of bytes. Each byte has a unique address, and every object in memory can be inspected as raw bytes.
// C Deep Dive: object representation
int x = 42;
unsigned char *bytes = (unsigned char*)&x;
printf("Byte 0: 0x%02x\n", bytes[0]);
printf("Byte 1: 0x%02x\n", bytes[1]);
// On little-endian: 0x2a, 0x00 (for int32)Key concepts:
- Object: a region of storage that holds a value of a specific type.
- Representation: the sequence of bytes that encodes the value.
sizeof: the number of bytes an object occupies.
// C Deep Dive: sizes of common types
printf("sizeof(char): %zu\n", sizeof(char)); // always 1
printf("sizeof(int): %zu\n", sizeof(int)); // usually 4
printf("sizeof(float): %zu\n", sizeof(float)); // usually 4
printf("sizeof(double): %zu\n", sizeof(double)); // usually 8
printf("sizeof(int*): %zu\n", sizeof(int*)); // 4 or 8The size of a type determines how many bytes pointer arithmetic advances. sizeof(char) is always 1 by definition -- it is the fundamental unit of storage in C.
C Deep Dive: C guarantees that any object can be copied byte-by-byte using
memcpy. This is the basis for serialization and deserialization. Auto provides the same guarantee for its value types.
12.2 Unions
A union is a type where all members share the same memory location. The size of a union equals the size of its largest member:
// C Deep Dive: union
union Value {
int i;
float f;
char s[16];
};
union Value v;
v.i = 42; // store as int
printf("%d\n", v.i); // read as int: 42
v.f = 3.14f; // overwrite with float
printf("%f\n", v.f); // read as float: 3.14
// printf("%d\n", v.i); // UNDEFINED BEHAVIOR: wrong type!Reading from a member that was not the last one written is undefined behavior in standard C (with exceptions for signedness variants). This makes bare unions dangerous.
Tagged unions solve this by pairing a discriminant (tag) with the union:
// C Deep Dive: tagged union
enum ValueType { VAL_INT, VAL_FLOAT, VAL_STRING };
struct TaggedValue {
enum ValueType tag;
union {
int i;
float f;
char s[16];
} data;
};
void describe(struct TaggedValue v) {
switch (v.tag) {
case VAL_INT: printf("integer: %d\n", v.data.i); break;
case VAL_FLOAT: printf("float: %f\n", v.data.f); break;
case VAL_STRING: printf("string: %s\n", v.data.s); break;
}
}Auto's replacement: enum with data. Auto provides tagged unions as a first-class feature, eliminating the manual bookkeeping:
enum Value {
IntVal int
FloatVal float
StrVal str
}
fn describe(v Value) str {
is v {
IntVal(n) => "integer: " + str(n)
FloatVal(f) => "float: " + str(f)
StrVal(s) => "string: " + s
}
}