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 + nandp - nproduce a pointer that many elements further along / behindp - q(two pointers of the same type) produces the element distance between them as aptrdiff_tp[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)walksnbytes usingchar *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 + 1is0x1004, not0x1001, because oneintis4bytes.*(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:
- State what the base points at and how long the range is (half-open
[begin, end)is the common idiom). - Step with
++porp + n; only usep[i]wheniis logical indexing. - Stop at
end; do not touch*end. Comparing againstendis fine, dereferencing it is not. - When casting between pointer types, assume the next arithmetic step will surprise you and verify byte offsets.
Check Yourself
- If
ppoints intoint a[10]andp == &a[3], what isp + 2? In element units? In byte units? - Why does
char *s = "hello"; s++;land on'e'rather than some other byte? - 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.