5.1.9 · D2C Programming

Visual walkthrough — Pointer arithmetic — adding integers to pointers

2,108 words10 min readBack to topic

We are deriving the central result of the parent topic:

But we will earn every symbol in that line before we use it.


Step 1 — What memory actually is: a row of numbered boxes

WHAT. Computer memory is one long line of tiny boxes. Each box holds exactly one byte (a byte = 8 bits, a small integer between 0 and 255). Each box has a house-number called its address. Addresses count up by 1: box 0, box 1, box 2, ...

WHY. Before we can talk about "adding to a pointer" we must agree what a pointer is. A pointer is nothing more than one of those house-numbers written on a slip of paper — it names a box. So "pointer arithmetic" will just be arithmetic on house-numbers. We need the picture of the street first.

PICTURE. Look at the row of boxes below. Each box is one byte; the number underneath is its address. The orange slip of paper is a pointer — right now it says "I name box 0x200."

Figure — Pointer arithmetic — adding integers to pointers

Step 2 — A value bigger than one byte needs several boxes

WHAT. An int (a whole number in C) usually does not fit in one byte — it commonly needs 4 boxes side by side. A double needs 8. A char needs exactly 1. The number of boxes a type T occupies is written .

WHY. This is the whole reason pointer arithmetic is interesting. If everything were one byte, "+1 address" and "+1 value" would be the same. Because a value can span multiple boxes, we must decide: does "+1" mean next box or next value? The figure shows why the question even exists.

PICTURE. One int painted across 4 consecutive boxes. The symbol labels the width. See how the value 10 occupies addresses 0x2000x203 all together.

Figure — Pointer arithmetic — adding integers to pointers

  • — the stride, a plain positive integer (4 for a typical int).
  • — the type we point at (int, char, double, ...). See sizeof operator.

Step 3 — An array packs values back-to-back with no gaps

WHAT. When you write T arr[N], C lays the N values contiguously — glued edge to edge, no empty boxes between them. Element arr[0] sits first, then arr[1] immediately after it, and so on.

WHY. Contiguity is the promise that makes the whole scheme work. Because there are no gaps, the position of element i is perfectly predictable: it is just i widths past the start. If arrays had random gaps, no arithmetic rule could find element i. (This packing is also what lets an array's name become a pointer to its first box.)

PICTURE. Four ints in a row. Element 0 starts at ; element 1 starts one stride later at ; element 2 at . Each colored block is sizeof(int) = 4 boxes wide.

Figure — Pointer arithmetic — adding integers to pointers

Let = the address of the very first box of arr (where arr[0] begins).

Read it aloud: "to reach element i, start at A, then skip i whole elements, each s boxes wide."


Step 4 — The demand that fixes the rule

WHAT. We now set up a pointer p = arr, so p holds the start address . We then make one demand: writing *(p + i) must give us arr[i]. (Here * is the dereference operator — "read the value in the box this address names".)

WHY. We are not discovering the pointer rule; the language designers chose it. They wanted the tidy identity arr[i] = *(p + i). That desire alone forces what p + i must compute — we reverse-engineer it below.

PICTURE. The pointer slip p starts pointing at arr[0]. We want p + 2 to point where arr[2] begins. The dashed orange arrow shows the jump we are demanding.

Figure — Pointer arithmetic — adding integers to pointers

For *(p + i) to equal arr[i], the address p + i must equal : The ! over = means "we are forcing this to hold."


Step 5 — Solve the demand: the scaling rule appears

WHAT. Since p already holds , substitute and read off what + i must do.

WHY. This is the payoff step — the parent note's central formula is derived, not assumed. We literally solve for the definition of + on a pointer.

PICTURE. The two expressions laid on the same number line of addresses: plain p sits at ; p + i must sit at ; so the arrow labelled "+ i" has length boxes — scaled by the stride.

Figure — Pointer arithmetic — adding integers to pointers

Start from the demand and cancel :


Step 6 — Same +1, three types, three different jumps

WHAT. Because the stride lives inside the pointer's type, the identical source code ptr + 1 moves different numbers of bytes for char*, int*, double*.

WHY. This is the fact beginners trip on. The + 1 looks the same; the type silently changes the meaning. Seeing all three strides at once cements that the type — not the number — carries the stride.

PICTURE. Three pointers all starting at 0x1000. The char* arrow lands at 0x1001 (1 box). The int* arrow lands at 0x1004 (4 boxes). The double* arrow lands at 0x1008 (8 boxes).

Figure — Pointer arithmetic — adding integers to pointers

Step 7 — Edge case: n = 0 and negative n

WHAT. The rule was derived for any integer n. Check the corners:

  • : adds bytes → p + 0 is p itself (stays put).
  • : e.g. p - 1 subtracts bytes → steps backward one element.

WHY. A rule you trust must survive its degenerate inputs. Zero must be a no-op; negatives must walk left. If either broke, arr[i] indexing would fail at the ends of loops.

PICTURE. From a pointer sitting on arr[2]: +0 stays, -1 jumps left to arr[1], +1 jumps right to arr[3]. All arrows are one stride long.

Figure — Pointer arithmetic — adding integers to pointers

where is p's current address. (Subtracting two pointers is a different operation — see Pointer subtraction and ptrdiff_t.)


Step 8 — Edge case: the one-past-the-end address

WHAT. For T arr[N], forming arr + N is legal — it names the box just after the last element. But dereferencing it (*(arr + N)) is undefined behavior.

WHY. Loops need a "stop" address, so C guarantees you may compute one-past-the-end safely. Yet that box is not yours to read or write — it may belong to something else. Reading it is undefined behavior: the program may do anything.

PICTURE. Array of 4 ints. Valid-to-dereference boxes arr[0..3] shown green. The arr + 4 slot is marked amber "legal to form, illegal to read"; anything further (arr + 5) is red "illegal even to form".

Figure — Pointer arithmetic — adding integers to pointers

The one-picture summary

Everything above compressed into a single diagram: the start , the stride , the scaled jump , and the identity arr[i] == *(arr + i) reading off the same box.

Figure — Pointer arithmetic — adding integers to pointers
Recall Feynman: retell the whole walkthrough in plain words

Memory is a street of identical little boxes, each numbered — that number is an address, and a pointer is just a slip of paper carrying one such number (Step 1). But a real value like an int doesn't fit in one box; it stretches across several — 4 for a typical int — and that width is sizeof (Step 2). When we make an array, C glues the values edge to edge with no gaps, so element i always begins exactly i widths past the start A (Step 3). Now we demand the friendly rule *(p + i) == arr[i] (Step 4), and solving that demand tells us the only thing + i can mean: add i × width to the address (Step 5). That's why the very same + 1 jumps 1 box for a char, 4 for an int, 8 for a double — the width hides inside the type (Step 6). Adding 0 keeps you put and subtracting steps you backward (Step 7), and you may point just past the last box to stop a loop, but you must never read that box (Step 8). One sentence carries it all: step by type, not by byte.


Active Recall

Which quantity does the "width of one element" equal in the scaling rule?
sizeof(*p), the stride s.
In arr[i] = A + i·s, what is A?
The byte address where arr[0] begins (start of the array).
Why does the same ptr + 1 move different byte counts for char* vs int*?
The stride sizeof(*ptr) differs (1 vs 4); the type carries the width.
What does p + 0 evaluate to?
p unchanged — it adds 0 × s = 0 bytes.
Is forming arr + N legal? Is dereferencing it legal?
Forming it is legal (one-past-end); dereferencing it is undefined behavior.
What single demand forces the entire scaling rule?
That *(p + i) must equal arr[i].

Connections

Concept Map

names

defines s

gives A plus i s

solves to

explains

handles

bounds

Memory is numbered byte boxes

A value spans sizeof T boxes

Array packs values no gaps

Demand star of p plus i equals arr i

Scaling rule add n times s

Same plus 1 different stride

Zero and negative n

One past end form ok deref UB