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​