5.1.10 · D2C Programming

Visual walkthrough — Arrays and pointers — array name decays to pointer

2,032 words9 min readBack to topic

This page goes deeper than the parent note on one thing: the mechanical, byte-by-byte derivation of indexing. Keep Pointer Arithmetic and Memory Layout and Addresses open in another tab — we lean on both.


Step 1 — Memory is a numbered street of boxes

WHAT. Before we say a single word about arrays, look at what "memory" is: a long row of one-byte boxes, each with a number (its address). Think house numbers on a street.

WHY. Every later idea — pointers, decay, indexing — is just arithmetic on these house numbers. If we don't nail down "memory = numbered boxes", nothing else can be earned honestly.

PICTURE. The street below runs left to right. Each little cell is one byte. The number under a cell is its address.

Figure — Arrays and pointers — array name decays to pointer

Step 2 — An array parks contiguous boxes and gives them one name

WHAT. When you write int a[4];, C reserves 4 ints in a row — no gaps. On a typical machine an int is 4 bytes, so the array occupies consecutive boxes. The name a is painted on this whole block.

WHY. "Contiguous" (no gaps) is the secret ingredient. Because element always sits exactly elements past the start, we will later be able to compute its location instead of searching for it.

PICTURE. The four ints a[0] a[1] a[2] a[3] are the four wide cells. The base address (address of a[0]) is 1000; each int is 4 bytes wide, so the starts fall on 1000, 1004, 1008, 1012.

Figure — Arrays and pointers — array name decays to pointer

Step 3 — Say the name in a value spot → it decays to an arrow

WHAT. The moment you use a where a value is expected (like in a + 1, or int *p = a;), C does not hand you all 16 bytes. It quietly converts a into a small arrow pointing at a[0]. That arrow is a pointer; its stored value is the address 1000.

WHY this conversion at all? Carrying 16 (or 4000) bytes around is wasteful. An arrow is just one address — cheap to copy. So C collapses the name into "here's where it starts." This collapse is called decay (see Passing Arrays to Functions for the payoff).

WHY an arrow to a[0] specifically (not a[2])? Because the front of the block is the only landmark every element can be measured from. Give me the front + a count and I can reach any car.

PICTURE. The name tag a dissolves into a burnt-orange arrow whose tip lands on box 1000. The pointer p now literally stores the number 1000.

Figure — Arrays and pointers — array name decays to pointer

Step 4 — Adding 1 to a pointer moves by one element, not one byte

WHAT. Here is the rule that makes indexing work: for a pointer of type int*, p + 1 is not address . It is address — the next int.

WHY does C scale by the element size? Because a pointer knows the type it points at. If p + 1 only moved one byte, it would land inside a[0], halfway through an integer — useless. Scaling by sizeof(int) guarantees p + 1 always lands on a whole element. This is the whole reason pointer arithmetic exists (full story: Pointer Arithmetic).

PICTURE. Three arrows fan out from the base: p+0 → 1000, p+1 → 1004, p+2 → 1008. Under each hop is the label +4 bytes, but the count on top is +0, +1, +2.

Figure — Arrays and pointers — array name decays to pointer

Step 5 — The star * reads the box the arrow points at

WHAT. *(...) is the dereference operator: "go to the address inside the parentheses and read the value living there." So *(p + 2) means: compute the address 1008, then hand back the int stored in that box.

WHY do we need a separate operator? Step 4 only gave us a location (1008). We usually want the contents of that location, not the number itself. * is the "open the box and take out what's inside" step.

PICTURE. The arrow p+2 points at box 1008; a plum bubble labelled *(p+2) reaches into that box and pulls out its value (say 70).

Figure — Arrays and pointers — array name decays to pointer

Step 6 — Snap it together: a[i] is defined as *(a + i)

WHAT. Now chain Steps 3–5 for the expression a[i]:

  • a decays → arrow at 1000 (Step 3),
  • a + i → address (Step 4),
  • *(a + i) → read the int there (Step 5).

That is exactly "the -th box". So the C standard simply defines the bracket notation as this chain:

WHY is this "just notation"? Because there is no separate machinery for []. The brackets are pure sugar for one decay + one add + one dereference. Nothing else is happening.

THE FUN CONSEQUENCE. Ordinary + doesn't care about order: a + i == i + a. So That is why 2[a] compiles and equals a[2] — living proof that indexing is only arithmetic.

PICTURE. Two roads leading to the same box 1008: the left road labelled a[2], the right road labelled 2[a], both funnelling through *(1000 + 2×4).

Figure — Arrays and pointers — array name decays to pointer

Step 7 — Edge case: change the pointer's type, change the step

WHAT. a decays to int* (steps by 4). But &a is different: its type is int(*)[4] — a pointer to the whole array. Numerically &a and a are the same address 1000, yet +1 behaves differently.

WHY show this? It proves the × sizeof in Step 4 is driven by type, not by the address value. Same starting number, two different jump sizes:

  • int *p = a;p + 1 = 1004 (step = sizeof(int) = 4)
  • int (*q)[4] = &a;q + 1 = 1016 (step = sizeof(int[4]) = 16)

This is exactly the sizeof/&arr non-decay behaviour from the parent, seen as geometry. (More: sizeof Operator and Multidimensional Arrays, where decay peels only the outer dimension.)

PICTURE. Two arrows leave the same base 1000: the teal int* makes a short +4 hop; the plum int(*)[4] makes a long +16 hop that clears the entire block.

Figure — Arrays and pointers — array name decays to pointer

Step 8 — Degenerate case: a[0], and why the array never moves

WHAT. Plug i = 0: a[0] == *(a + 0) == *a. The address is 1000 + 0×4 = 1000 — the base itself. So *a and a[0] are the same box.

WHY mention it? It shows the base is just element (no off-by-one), and it exposes a limit of decay: you cannot write a = p;. The name a is not a storage slot you can repoint — it is the fixed block. Only its decayed copy (a real pointer variable like p) can be reassigned.

PICTURE. i = 0 collapses the fan of arrows from Step 4 back onto the single base box 1000; a struck-through a = p; reminds us the name itself can't be moved.

Figure — Arrays and pointers — array name decays to pointer
Recall

*a equals which element? ::: a[0] — because a[0] == *(a+0) == *a. Can you reassign a with a = p;? ::: No — the array name is not an lvalue you can repoint; only a real pointer variable can be reassigned.


The one-picture summary

Everything above is one straight pipeline: name → decays to arrow → add scaled by type → dereference → the box.

Figure — Arrays and pointers — array name decays to pointer
Recall Feynman retelling of the whole walkthrough

Memory is a street of numbered boxes. When you declare int a[4], four int-sized boxes park in a row and get one name painted on the block. The instant you use that name in a value spot, C throws away the block and keeps only a little arrow pointing at the front box — that's decay. Now a[i] is a three-move dance: the name becomes the arrow (front address), you slide the arrow forward by i elements — and because the arrow knows it points at ints, "forward by i" secretly means "forward by i × 4 bytes" — and finally the star * opens whatever box the arrow now rests on. That's it. Because sliding forward is plain addition, i + a reaches the same box, so i[a] == a[i]. Swap the arrow's type (as &a does) and the slide-length changes even though the starting number is identical. And with i = 0 you land right back on the front box — which is why the name can never be told to point somewhere else: it is the street, not an arrow you carry.


Connections

  • Pointer Arithmetic — Steps 4 & 7 are exactly this.
  • sizeof Operator — the hidden multiplier in every hop.
  • Passing Arrays to Functions — decay (Step 3) is why size is lost at a call.
  • Multidimensional Arrays — decay peels only the outer dimension.
  • Memory Layout and Addresses — the numbered-boxes picture of Step 1.
  • Strings in Cchar[] vs char* seen through the same lens.