5.1.9 · D4C Programming

Exercises — Pointer arithmetic — adding integers to pointers

2,279 words10 min readBack to topic

The one rule we lean on everywhere (from the parent note):

We'll assume the common sizes unless a problem says otherwise: sizeof(char)==1, sizeof(int)==4, sizeof(double)==8, sizeof(short)==2. A picture of the "locker hallway" model to keep in mind:

Figure — Pointer arithmetic — adding integers to pointers

Level 1 — Recognition

Can you read a pointer expression and say what it means?

Recall Solution L1-A

p points at a[0]. Adding 2 steps two elements forward (not two bytes): p + 2 points at a[2]. Dereferencing gives the value stored there. Answer: 30.

Recall Solution L1-B

By the array–pointer identity, indexing is just scaled addition then a dereference: Both *(arr+3) and *(3+arr) are correct because + is commutative. (3[arr] is also legal but that isn't "only * and +".) Answer: *(arr + 3) and *(3 + arr).

Recall Solution L1-C

Stride for a char* is sizeof(char) = 1 byte. So five steps five bytes. Answer: 0x2005.


Level 2 — Application

Plug into the rule with real strides and addresses.

Recall Solution L2-A

Stride sizeof(int) = 4. Three steps: ( in hex is 0xC.) Answer: 0x40C.

Recall Solution L2-B

As a double*, stride is 8: Cast to char* first, and the stride collapses to 1: 0x1004 lands inside the first double (halfway through its 8 bytes) — usually a bug. See Strings and char pointers. Answer: d + 4 = 0x1020; ((char*)d)+4 = 0x1004.

Recall Solution L2-C

Offset in bytes bytes. Answer: offset 10 bytes; address 0x80A.


Level 3 — Analysis

Reason about what breaks, what's legal, and precedence.

Recall Solution L3-A

* binds tighter than +, so *p + 1 parses as (*p) + 1.

  • *p + 1 = a[0] + 1 = 7 + 1 = 8
  • *(p + 1) = a[1] = 8

They give the same number here by coincidence (), but for different reasons. Change the array to {7, 100, 9} and they diverge: *p+1 = 8 but *(p+1) = 100. See Dereference operator and precedence. Answer: *p + 1 = 8 (value-plus-one), *(p + 1) = 8 (next element). Same digit, different meaning.

Recall Solution L3-B
  • p + 4 → the one-past-the-end pointer. Legal to form, illegal to dereference (undefined behavior).
  • p + 5 → past one-past-the-end. Illegal even to form — computing it is already undefined behavior.
  • p - 1 → before the array. Illegal even to form (undefined behavior).

The only "safe zone" for forming is indices 0 through N inclusive; the safe zone for dereferencing is 0 through N-1. See Undefined behavior and bounds. Answer: p+4 form-legal/deref-UB; p+5 UB to form; p-1 UB to form.

Recall Solution L3-C
  • p + q is illegal — "address plus address" is meaningless; the standard defines only pointer ± int and pointer - pointer.
  • q - p is legal and gives the element count between them (type ptrdiff_t): Note: not bytes — the compiler divides the byte difference by sizeof(int). See Pointer subtraction and ptrdiff_t. Answer: p+q illegal; q-p = 3.

Level 4 — Synthesis

Combine the rule with loops and identities to produce answers.

Recall Solution L4-A

p++ is p = p + 1: each iteration advances one int. We read the current element then step forward:

  • k=0: read a[0]=2, sum=2
  • k=1: read a[1]=4, sum=6
  • k=2: read a[2]=6, sum=12
  • k=3: read a[3]=8, sum=20
  • k=4: read a[4]=10, sum=30

After the loop p points at a[5] (one-past-end) — held, never dereferenced. See Arrays decay to pointers. Answer: sum == 30.

Figure — Pointer arithmetic — adding integers to pointers
Recall Solution L4-B

p starts at a[2] (value 15). Now use the identity x[y] == *(x+y):

  • p[2] = *(p + 2) = *(a + 4) = a[4] = 25
  • *(p - 1) = *(a + 1) = a[1] = 10
  • 2[p] = *(2 + p) = *(p + 2) = 25 (commutativity of +)

Answer: p[2] = 25, *(p-1) = 10, 2[p] = 25.

Recall Solution L4-C

Cast to char* gives stride 1, so +12 moves exactly 12 bytes: 0x500 + 12 = 0x50C. Casting back to int*, we ask which element sits at offset 12 bytes: . So it points at a[3]. Because 12 is an exact multiple of 4, we land cleanly on an element boundary (no bug this time). Answer: &a[3], address 0x50C.


Level 5 — Mastery

Prove, generalize, and reason about strides you must derive yourself.

Recall Solution L5-A

Let the array start at byte address and let . Then: Pointer subtraction is defined as the byte difference divided by the element size: The cancels and the divides out — so the result is i - j independent of the stride. For : . Answer: proven; numeric value 5.

Recall Solution L5-B

The stride for a struct Pt* is sizeof(struct Pt) = 8, not the size of any single field: p[3] begins at 0x918. Its field y sits after x (an int, 4 bytes) inside the struct, so y is at byte offset 4 within that struct: Total byte offset from p to p[3].y is . Answer: p + 3 = 0x918; &p[3].y = 0x91C; offset 28 bytes.

Recall Solution L5-C

Work left to right, tracking the current pointer type because the stride follows the type:

  1. L + 1: L is long*, stride 8. → 0x2000 + 8 = 0x2008.
  2. (int*)(...): reinterpret that address as an int*. Address unchanged: 0x2008, but stride now 4.
  3. ... + 1: now an int*, stride 4. → 0x2008 + 4 = 0x200C. The lesson: a cast changes the stride of future arithmetic, never the current address. See Multidimensional arrays and pointers where this stride-switching drives row/element navigation. Answer: 0x200C.

Recall

Recall What did each level test?

L1 :: reading an expression's meaning (element vs byte). L2 :: substituting real strides and addresses (watch the hex). L3 :: precedence, legality/UB, and pointer±pointer rules. L4 :: loops, the x[y]==*(x+y) identity, and cast-driven byte moves. L5 :: deriving strides yourself — subtraction proof, struct stride, mixed casts.


Connections