The problem › What is memory, actually?
One giant array of bytes
Every diagram you have ever seen of a program, whether objects pointing at objects, trees, linked lists, or tensors, is a fiction drawn on top of something much dumber. To a machine-level program, memory is one very large array of bytes, and every byte is identified by a unique number: its address. The set of all possible addresses is the address space. There is nothing else down there: no types, no objects, no rows or columns, just bytes at addresses.
The byte, 8 bits, is the smallest unit you can address. You cannot ask memory for a single bit; bits come in named 8-packs, and every value in every program you've ever run is some run of these packs.
A pointeris not a mysterious C concept; it is an index into this array, stored as an ordinary integer. The machine's word size is exactly the size of that integer: on a 64-bit machine, addresses are 64-bit values, which is why sizeof(void*)is 8 and why the address space tops out at 2⁶⁴ bytes: 16 exabytes, about 1.84 × 10¹⁹. When people say "a 64-bit machine," this is what the 64 bits are for: naming locations in the byte array.
One more thing the sources are blunt about, and it's the punchline of this whole chapter: the compiler tracks types, but the machine code it emits has no information about data types at all. At runtime, everything, whether data, instructions, or pointers, is just blocks of bytes. Everything this curriculum does later, from caches to coalescing to KV caches, happens to ranges of this array. If you can picture the array, you can picture all of it.
The mechanism › Types are just interpretations
An int is four bytes wearing a costume
The byte array stores bytes; types live only in the eye of the beholder. When a program declares int x, the compiler reserves 4 consecutive bytes and writes the value's two's-complement bit pattern into them. A float reserves 4 bytes with a completely different encoding; a double, 8; a char, exactly 1; a pointer, the full 8-byte word.Cast a pointer and you can read the same 4 bytes back as either type: the bytes don't change, only the interpretation does.
Two conventions pin down exactly where a multi-byte value lives. First: it is stored as a contiguous run of bytes, and its address is the smallest address in the run: a 4-byte int at address 0x100 occupies 0x100 through 0x103. Second: the order of bytes within the run is a genuine hardware choice. Little-endian machines store the least significant byte first (at the lowest address); big-endian machines store the most significant byte first.
In practice you will live on little-endian: x86 is exclusively little-endian, and while ARM hardware is technically bi-endian, both Android and iOS run it little-endian only.
Predict first
A double sits at address 2000. What is the address of its last byte?
SourcesCS:APP 3e §2.1.2–2.1.3 · pp. 75–84
The mechanism › Aggregates
Arrays and structs are just adjacency
An array is the byte array's native data structure. The declaration T A[N] does exactly two things: it allocates one contiguous region of L · N bytes (where L is the size of type T), and it makes Aa name for the region's starting address. Element i lives at address + L · i. Full stop: that formula is the entire implementation.
The hardware is built for this: x86-64 computes base + 4·i and loads the result in a single instruction, movl (%rdx,%rcx,4),%eax, with hardware scaling for element sizes 1, 2, 4, and 8. No lookup table, no magic, one add and one shift baked into the addressing mode.C's pointer arithmetic is the same formula wearing different syntax: A[i] is defined as *(A+i), and adding an integer to a pointer secretly scales it by L.
Two-dimensional arrays are the same trick applied twice. C stores T D[R][C] in row-major order, all of row 0, then all of row 1, so element D[i][j] sits at address + L(C · i + j). Memorize the shape of that formula: the row index is multiplied by the whole row width. Walk j and you move 4 bytes at a time; walk i and you leap 4C bytes at a time. Both are one multiply and one add, but they generate wildly different address sequences, the difference that chapter 0.6 will turn into a 25× runtime gap.
A struct is adjacency with mixed sizes: all fields live in one contiguous region, in declaration order, and a pointer to the struct is the address of its first byte. The compiler knows each field's byte offset at compile time and bakes it into the instruction as a constant displacement: r->j compiles to "load 4 bytes at address-of-r + 4." The machine code contains no field names, no field types; offsets are all that survive compilation.(The alignment gaps the compiler sometimes inserts between fields are chapter 0.7's subject.)
interactive · coming soon
ByteGrid: peek & poke
Planned interactive: a live byte grid where you drop an int, a float, a struct onto an address and watch which bytes light up; flip endianness; read raw bytes back as a different type.
SourcesCS:APP 3e §3.8.1–3.8.3 · pp. 291–295CS:APP 3e §3.9.1 · pp. 301–303
The number › Your turn
Where does a[17] live?
Compute the number › address arithmetic by hand
| float a[64] starts at | 0x1000 (= 4096) |
| element size L (float) | 4 bytes |
| offset of a[17] | 17 × 4 = 68 bytes |
| address of a[17] | 4096 + 68 = 4164 |
| bytes occupied | 4164, 4165, 4166, 4167 |
| Result | a[17] = address 4164 (0x1044) |
This is CS:APP's xA + L · i formula (§3.8.1) applied once. Nothing you will compute in this course, not cache-set indices, not GPU coalescing checks, not KV-cache offsets, is harder than this line of arithmetic. It only ever gets more consequential.
The full story › what the address really is
The addresses your program sees are virtual. CS:APP introduces the byte array as "virtual memory" in its very first sentence on the topic (§2.1, p. 70). The flat array is a conceptual image presented to your program; the actual implementation stitches it together from DRAM, disk, special hardware, and operating-system software. That machinery of pages, page tables, and TLBs is chapter 0.8, and it becomes the mental model for vLLM's paged KV cache in Topic 3. For now the fiction is airtight: one process, one flat byte array.
Also deliberately deferred: why the "array" is not uniform in speed. Address 4164 might answer in 1 ns or 90 ns depending on recent history. That crack in the abstraction is where the entire rest of Topic 0 lives.
Memory is one flat array of bytes. A pointer is an index. A type is an interpretation stamped on a run of consecutive bytes; the machine code itself keeps only sizes and offsets. Every performance question in this course is about which runs you touch, in what order.