Arrays & Pointer Arithmetic
Why a[i] is literally *(a + i)
- › Explain that an array name decays to the address of its first element
- › Derive the identity a[i] == *(a + i), and why i[a] also compiles
- › Predict how pointer arithmetic scales by element size
- › Connect array indexing to the [base + index*scale] addressing mode from day 6
Arrays and pointers are two views of one thing
An array in C is a block of equal-sized elements laid out contiguously in memory — element 0,
then element 1 right after it, and so on. The name of the array, used in an expression, decays to a
pointer to its first element. So a (an int array) is, in most contexts, just &a[0] — an
address. Arrays and pointers aren't the same thing, but array *indexing* is defined entirely in terms
of pointer arithmetic, which is why they feel intertwined.
The identity that explains everything
Here is the definition of subscripting in C, and it's worth carving into your memory:
a, add i, dereference." That's the whole rule. And because a + i and i + a are the
same, a[i] equals i[a] — a legal (if cursed) way to write it that proves indexing is just
commutative pointer addition, not a special array operation.But "add i" means "add i elements," not i bytes
The subtlety that makes it work: pointer arithmetic scales by the size of the pointee. a + 1
on an int* (4-byte ints) doesn't move 1 byte — it moves 4 bytes, to the next int. a + i moves
i * sizeof(element) bytes. This is why the type on a pointer matters (day 16): the type is what the
compiler multiplies by.
a[0] a[1] a[2] a[3]
┌──────┬──────┬──────┬──────┐
│ 10 │ 20 │ 30 │ 40 │ each cell = 4 bytes (int)
└──────┴──────┴──────┴──────┘
0x1000 0x1004 0x1008 0x100c
a = 0x1000 (address of a[0])
a + 2 = 0x1008 (NOT 0x1002 — scaled by 4)
*(a + 2) = a[2] = 30Look at that address: a + 2 is 0x1000 + 2*4 = 0x1008, exactly where a[2] lives. Now recall
day 6's addressing mode: [base + index*scale], with scale = element size. a[i] compiles to
[a + i*4] — the array's base, the index, times 4 (the int size). You already learned to read
array indexing in assembly; this is the same thing in C. The C a[i], the pointer expression
*(a+i), and the machine's [base + index*scale] are three spellings of one operation.
1int a[4] = {10, 20, 30, 40};2int x = a[2]; // 303int y = *(a + 2); // 30, identical to a[2]4int z = 2[a]; // 30 too — cursed but valid5 6// walking with a pointer:7for (int *p = a; p < a + 4; p++) {8 // *p is a[0], a[1], a[2], a[3] in turn; p++ moves 4 bytes each step9}Why there's no bounds checking
C computes a + i and dereferences it — it does not check that i is within the array. a[7]
on a 4-element array cheerfully reads *(a + 7), memory past the end of the array. There's no guard;
the language trusts you. That's fast and dangerous in equal measure — reading or writing out of
bounds is undefined behaviour (day 28) and the seed of countless bugs and exploits (recall the stack
buffer from day 14: writing buf[i] with a too-large i walks straight toward the return address).
long b[3]; starts at address 0x2000. long is 8 bytes. What address does b[2] (i.e. *(b+2))
read from? And what address would the out-of-bounds b[3] touch?a++ on an array, and sizeof(a) gives the whole array's byte size, while
sizeof(p) on a pointer gives 8. Also, when you pass an array to a function it decays to a bare
pointer, so the function can't know its length — you must pass the length separately. This is why C
string/array functions always take a size.Prove the identity, then walk memory
In arrays.c: (1) declare an int array and verify a[i] == *(a+i) == i[a] for a couple of indices by printing all three. (2) Print &a[0], &a[1], &a[2] and confirm the addresses differ by exactly sizeof(int). (3) Walk the array two ways — by index a[i], and by a moving pointer p++ — and show they visit the same elements. Then compile at -O0 and find the [base + index*scale] operand for the indexing. Uses only this lesson plus day 6/16.
$ gcc -O0 -g -Wall arrays.c -o arrays && ./arrays$ objdump -d -M intel arrays- › You showed a[i], *(a+i), and i[a] give the same value
- › Consecutive element addresses differ by exactly sizeof(int)
- › You walked the array with a pointer (p++) and by index, matching results
- › You found the [base + index*scale] addressing mode in the disassembly