Skip to main content

Pointer Arithmetic and the Type-Scaled Step

What This Concept Is

When you add an integer to a pointer, the address does not change by that integer. It changes by integer * sizeof(*p), measured in bytes. That implicit multiplication is the whole point of typed pointers.

Three operations are defined:

  • p + n and p - n produce a pointer that many elements further along / behind
  • p - q (two pointers of the same type) produces the element distance between them as a ptrdiff_t
  • p[n] is literally defined as *(p + n)

Comparisons (<, <=, ==) are defined only between pointers into the same array (or one past the end). Outside that, results are undefined.

Why It Matters Here

Every walk through an array, buffer, or memory region is pointer arithmetic:

  • string traversal: while (*s++) ...
  • memcpy(dst, src, n) walks n bytes using char * arithmetic
  • parsing a binary record reads uint32_t *-style pointers at specific offsets
  • treating a struct field as an offset from the base pointer is exactly ptr + offset

A single mismatched type in pointer arithmetic is a silent off-by-sizeof(T) bug.

Concrete Example

int a[4] = {10, 20, 30, 40};
int *p = a; /* array decays to pointer to its first element */

Assume a[0] is at address 0x1000 and sizeof(int) = 4:

Index:     [0]      [1]      [2]      [3]
Address: 0x1000 0x1004 0x1008 0x100C
Value: 10 20 30 40
  • p + 1 is 0x1004, not 0x1001, because one int is 4 bytes.
  • *(p + 2) == a[2] == 30.
  • &a[3] - &a[0] == 3 (element count, not byte count).

Now with a different type:

char *c = (char *)p;
c + 1; /* 0x1001, one byte forward */

The address expression changed because the type changed, not because of anything else.

Common Confusion / Misconception

"p + 1 moves one byte." Only if *p is one byte (char, uint8_t). For int *p, p + 1 moves four bytes on most platforms. The multiplier is sizeof(*p).

"I can compare any two pointers." You can compare for equality, but ordered comparisons between pointers that are not part of the same array object are undefined in C.

"a[i] is different from *(a + i)." They are literally the same expression by definition, which is why 5[a] also works (though nobody should write it).

How To Use It

For every pointer walk you write:

  1. State what the base points at and how long the range is (half-open [begin, end) is the common idiom).
  2. Step with ++p or p + n; only use p[i] when i is logical indexing.
  3. Stop at end; do not touch *end. Comparing against end is fine, dereferencing it is not.
  4. When casting between pointer types, assume the next arithmetic step will surprise you and verify byte offsets.

Check Yourself

  1. If p points into int a[10] and p == &a[3], what is p + 2? In element units? In byte units?
  2. Why does char *s = "hello"; s++; land on 'e' rather than some other byte?
  3. What is &a[0] - &a[0]? &a[0] + 0? Under what conditions is &a[10] even legal to form?

Mini Drill or Application

#include <stdio.h>

int main(void) {
int a[5] = {1, 2, 3, 4, 5};
int *p = a;
for (int *q = p; q < p + 5; q++) {
printf("addr=%p offset=%ld *q=%d\n",
(void *)q, (long)(q - p), *q);
}
return 0;
}

Build: gcc -Wall -Wextra -o ptr_arith ptr_arith.c. The difference between successive addr values should be sizeof(int). Repeat with long, char, and double. Write down the byte step each time.

Read This Only If Stuck