5.1.10 · D4C Programming

Exercises — Arrays and pointers — array name decays to pointer

2,277 words10 min readBack to topic

Assume a typical 64-bit machine for all problems: sizeof(int) == 4, sizeof(char) == 1, sizeof(double) == 8, and every pointer is 8 bytes. All the ideas come from the parent note on decay.

Figure — Arrays and pointers — array name decays to pointer

The picture above is the mental model for the whole page: an array name in a value context becomes a red arrow to box 0. Keep it in mind on every question.


Level 1 — Recognition

Can you spot whether decay happens and what the value/type is?

Recall Solution L1.1

The rule: names become arrows except S.A.S. (Sizeof, Ampersand, String-literal-init).

  • (a) a + 1 — value context → decays. a becomes int*, so a+1 has type int*.
  • (b) sizeof a — the S exception → does NOT decay. It sees the whole type int[10].
  • (c) &a — the A exception → does NOT decay. Type is int(*)[10].
  • (d) *aa decays to int*, then * dereferences it → type int, value a[0].
Recall Solution L1.2
  • a (in a value context) ::: int*
  • &a[0] ::: int* — the & here is on an element, not on a, so no whole-array pointer.
  • &a ::: int(*)[5] — pointer to the whole array.
  • a[2] ::: int — it's an element, not an address. Notice a and &a[0] are the same value and the same type int*. But &a shares the value while having a different type.

Level 2 — Application

Compute concrete sizes and addresses.

Recall Solution L2.1
  • sizeof(d) = — no decay, whole array.
  • sizeof(p) = p is a genuine pointer (8 bytes).
  • sizeof(d)/sizeof(d[0]) = — this is the famous element-count idiom, valid only where d is the real array (not decayed).
Recall Solution L2.1... L2.2

Pointer +1 steps by the size of the pointed-to type.

  • (a) a = 1000.
  • (b) a + 1a is int*, step = 4 → 1004.
  • (c) &a + 1&a is int(*)[6], step = whole array = 1024.
  • (d) &a[3] = 1000 + 3\times4 = 1012.
Recall Solution L2.3
  • sizeof(s) = — declared size, chars are 1 byte each: .
  • s[3]"hi" fills s[0]='h', s[1]='i', s[2]='\0', and s[3] is the leftover, zero-initialized to '\0' = 0.

Level 3 — Analysis

Explain a result, not just compute it.

Recall Solution L3.1

Prints 8. A parameter written int arr[100] (or int arr[], or int arr[999]) is rewritten by the compiler to int *arr — the bracket count is ignored for parameters. At the call, the caller's array already decayed to a pointer, so only the 8-byte address travels in. sizeof(arr) therefore measures a pointer. See Passing Arrays to Functions.

Recall Solution L3.2

By definition x[y] \equiv *(x+y). Addition is commutative, so: For -1[a+2]: treat x = -1, y = a+2: So -1[a+2] is just a[1]. (Ugly, legal, and proof that indexing is pure Pointer Arithmetic.)

Recall Solution L3.3
  • p is int*p++ advances bytes (one int).
  • q is int(*)[5]q++ advances bytes (one whole array). Same starting value, different type ⇒ different stride. The type is the "ruler" pointer arithmetic uses.

Level 4 — Synthesis

Combine decay with multidimensional arrays and function design.

Figure — Arrays and pointers — array name decays to pointer
Recall Solution L4.1

A 2D array is an array of arrays: m has type int[3][4], i.e. "3 elements, each an int[4]." Decay peels the outermost dimension only: a pointer to a row of 4 ints. So m + 1 steps by one row = bytes, landing on m[1]. See Multidimensional Arrays.

Recall Solution L4.2

The blank is the element count: sizeof(v)/sizeof(v[0]) = , so s = sum(v, 5). The result is 2+4+6+8+10 = 30. The function cannot compute n because inside sum, a is a decayed int*sizeof(a) would be 8 (pointer), giving the wrong count. The length must be measured where the array is still an array (in the caller) and passed explicitly.

Recall Solution L4.3

Because grid decays to int(*)[4], the parameter must be:

void print_grid(int (*g)[4], int rows)   // or equivalently int g[][4]

The inner dimension 4 must be given — the compiler needs it to compute g[i] (skip ints) and then [j]. The row count can be a runtime parameter, but the column count is baked into the pointer's type.


Level 5 — Mastery

Predict, prove, and reason about edge cases.

Recall Solution L5.1
  • *(a + 2) — move 2 ints, then read → a[2] = 30.
  • *a + 2*a is a[0]=10, then +2 (plain int add) → 12. Precedence: * binds before +.
  • 2[a] = *(2+a) = a[2] = 30.
  • sizeof a / sizeof *a = = 4 (element count idiom).
Recall Solution L5.2
  • a + 3 (address of a[3], one past the last element) is legal to form — the C standard specifically permits a "one-past-the-end" pointer for loop bounds like for (p = a; p < a+3; p++).
  • *(a + 3) is undefined behavior — you may compute that address but you may not dereference it. There is no element there. This is why the boundary is exactly n, not n-1 or n+1, in the loop condition.
Recall Solution L5.3
  • (a) p = a; ✅ — a decays to int*, assigned to a pointer.
  • (b) a = p; ❌ — an array name is not a modifiable lvalue; you cannot make the array point elsewhere.
  • (c) p = &a[0]; ✅ — same value as (a), explicit form.
  • (d) a++; ❌ — ++ needs a modifiable lvalue; a isn't one. (But p++ is fine.)

Recall Feynman recap — the whole ladder in one breath

An array name is a row of boxes with a name painted on the front. Say the name in most places and it turns into a red arrow to box 0 (decay). The arrow's type is the ruler: int* steps 4 bytes, int(*)[4] steps a whole row, int(*)[N] steps the whole array. sizeof, &arr, and string-literal init are the three places the name stays a full array and remembers its length. Everywhere else — index, pass, add — it's just pointer arithmetic in disguise.


Connections

  • Arrays and pointers — array name decays to pointer (index 5.1.10) — the parent this drills.
  • Pointer Arithmetic — the stride rule behind every L2/L3 answer.
  • Passing Arrays to Functions — L3.1 and L4.2.
  • sizeof Operator — L1/L2 the decay exception.
  • Multidimensional Arrays — L4 peeling one dimension.
  • Strings in C — L2.3 and the '\0' edge.
  • Memory Layout and Addresses — contiguity behind L4.3 and L5.2.