Skip to content
📖 Auto vs Modern CBook contents

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
// 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
// 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 8

The 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
// 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
// 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:

auto
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
    }
}

Released under the MIT License.