roadmap
week 1 · day 4

Memory & Addresses

RAM as one giant byte-addressed array

Easy 28 min 180 xp
After this you can
  • Picture RAM as a numbered array of bytes
  • Explain what an address is and how big one is on a 64-bit machine
  • Understand endianness and predict byte order in memory
  • Read the address and bytes of a variable using a program you write

Memory is one long ruler of bytes

Forget diagrams of chips. To a program, RAM is a single enormous array of bytes, numbered starting at 0. Each byte has a position in that array, and that position number is its address. Byte 0, byte 1, byte 2 … up into the billions. That's the whole model.

RAM as a byte array
 address: 0 1 2 3 4 5 6 7 ...
 +-----+-----+-----+-----+-----+-----+-----+-----+
 byte: | 00 | CD | FF | 41 | 00 | 00 | 00 | 07 | ...
 +-----+-----+-----+-----+-----+-----+-----+-----+
 ^ ^
 | this byte's address is 3, it holds 0x41 ('A')
 this byte's address is 0
Key idea
An address is just a number, the index into the byte array. A pointer (Week 3) is a variable that *holds* such a number. When you hear "pointer to x", read it as "the array index where x's first byte lives." Nothing more mystical than that. Everything about pointers later becomes easy if you hold this picture.

How big is an address?

On a 64-bit machine, an address is a 64-bit number, it fits in one register (that's a big reason registers are 64 bits). 64 bits can count up to about 1.8 × 10^19, which is why 64-bit machines can theoretically address a preposterous amount of memory. In practice the OS gives each program its own private, pretend address space starting near 0, and translates behind the scenes, but from your program's view it's just that clean numbered ruler.

A multi-byte value occupies consecutive addresses. A 4-byte int at address 100 uses bytes 100, 101, 102, 103. A pointer needs 8 bytes because an address is 64 bits. This is why Day 1's size table matters: sizes tell you how many array cells a value consumes.

Endianness: which byte comes first?

Here's a subtlety with real consequences. Say you store the 4-byte integer 0x0A0B0C0D at address 100. Which byte goes in cell 100, the big end (0x0A) or the little end (0x0D)? Both choices exist:

  • Big-endian: most-significant byte first. Cell 100 = 0x0A. (Reads like we write numbers.)
  • Little-endian: least-significant byte first. Cell 100 = 0x0D. (Backwards to human eyes.)

x86-64 is little-endian. Your machine stores the low byte at the lowest address. This looks bizarre in a memory dump, numbers appear byte-reversed, so you must know it or you'll misread every hexdump for the rest of your life.

0x0A0B0C0D stored little-endian at address 100
 the value: 0x 0A 0B 0C 0D
 hi lo

 address: 100 101 102 103
 +------+------+------+------+
 byte: | 0D | 0C | 0B | 0A |
 +------+------+------+------+
 lo hi <- low byte at the LOWEST address

Proving it with C

This program takes an int, prints its address, and then walks its 4 bytes in memory order so you can watch the little-endian layout with your own eyes.

memory.c, see an int's address and byte orderc
1#include <stdio.h>
2
3int main(void) {
4 int x = 0x0A0B0C0D; // a value with 4 distinct bytes
5 unsigned char *p = (unsigned char *)&x; // p = the address of x's first byte
6
7 printf("x lives at address %p\n", (void *)&x);
8 printf("sizeof(int) = %zu bytes\n", sizeof x);
9
10 for (size_t i = 0; i < sizeof x; i++) {
11 printf("byte %zu (addr %p) = 0x%02X\n", i, (void *)(p + i), p[i]);
12 }
13 return 0;
14}

Line by line

unsigned char *p = (unsigned char *)&x;, three ideas packed together: - `&x` is the address-of operator: it gives the address where `x` begins, its index in the byte ruler. - We store that address in `p`, a pointer to unsigned char**. Typing it as unsigned char * means "treat this address as pointing at plain bytes," so we can inspect x one byte at a time regardless of its real type. - The (unsigned char *) is a cast, we're telling the compiler "yes, deliberately view this int's address as a byte address." Week 3 makes casts precise; today just read it as "let me see the raw bytes."

`%p` prints a pointer (an address) in hex, you'll see something like 0x7ffe..., a real location in your program's address space.

`p[i]` reads the byte at address p + i. Because x86-64 is little-endian, byte 0 (the lowest address) prints as 0x0D, the *low* byte of the value, and byte 3 prints 0x0A. The value reads "backwards" in memory, exactly as the diagram predicts.

`p + i` is pointer arithmetic: since p points at bytes (1 byte each), p + 1 is the very next address. When a pointer points at wider things, + 1 steps by that thing's size, a crucial detail we'll unpack on Week 3 Day 17. For bytes it's the simplest case: step of 1.

Note
Run this twice. The byte order (0D 0C 0B 0A) is identical every run, that's the architecture. The address (%p) usually *changes* every run, that's the OS randomizing where your program loads, a security feature called ASLR. So: layout is fixed by the machine, location is chosen by the OS. Two different ideas, both visible in one small program.
finished reading?
Your task, you write the code

Dump the bytes of different types

Write memory.c from the lesson yourself. Then generalize: write a function dump(void *ptr, size_t n) that prints n bytes starting at ptr, each as two hex digits, in address order. Use it to dump an int (4 bytes), a long (8 bytes), and a short (2 bytes), each holding a value with visibly distinct bytes (e.g. 0x11223344). Confirm the little-endian ordering in each.

deliverable: memory.c
build & run
$ gcc -Wall -Wextra -o memory memory.c
$ ./memory
self-review before running
  • An int holding 0x0A0B0C0D dumps as 0D 0C 0B 0A
  • sizeof(long) prints 8 and sizeof(short) prints 2 on your machine
  • You can explain why the bytes appear reversed
  • Running twice, the byte pattern is stable but %p changes, and you know why
stretchDump a `float` holding 1.0f (4 bytes). The bytes will look strange (00 00 80 3F). You're not expected to decode IEEE-754 yet, just note that the *same byte-array model* holds even for floats. The interpretation differs; the memory model does not.

Self-check

01An address, at its core, is:
02On x86-64 (little-endian), the int 0x11223344 stored at address 200 puts which byte at address 200?
03Why is an address 8 bytes on a 64-bit machine?
04If p is an `unsigned char *`, then p + 1 refers to:
0/4 correct · 0/4 checked