Memory & Addresses
RAM as one giant byte-addressed array
- › 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.
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 0How 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.
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 addressProving 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.
1#include <stdio.h>2 3int main(void) {4 int x = 0x0A0B0C0D; // a value with 4 distinct bytes5 unsigned char *p = (unsigned char *)&x; // p = the address of x's first byte6 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.
%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.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.
$ gcc -Wall -Wextra -o memory memory.c$ ./memory- › 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