🎯 Pointers & Memory
Pointer arithmetic: +1 is not one byte
▶ Open the interactive lesson — free, no signupAny program that walks through a thousand scores or a million pixels needs "go to the next one", which in C is spelled p + 1 — and it secretly moves 4 or 8 bytes, not 1. Grasp that one rule and every walk-through-the-data loop you'll ever write makes sense. Miss it, and your code quietly reads memory that belongs to someone else.
Here's a question that separates people who know C from people who've merely seen it: if int *p holds the address 0x1000, what is p + 1? If you said 0x1001 — this lesson is for you.
The golden rule: p + 1 moves by sizeof(*p)
Pointer arithmetic counts in elements, not bytes. Adding 1 to an int * advances the address by sizeof(int) (4 bytes); adding 1 to a double * jumps 8; a char * moves 1. This is exactly why pointers carry a type: the type sets the stride — how far each step of arithmetic moves.
#include <stdio.h>
int main(void) {
int arr[4] = {10, 20, 30, 40};
int *p = arr;
char *c = (char *)arr; /* same address, char view */
printf("p = %p\n", (void *)p);
printf("p + 1 = %p (+%zu bytes)\n", (void *)(p + 1), sizeof *p);
printf("c = %p\n", (void *)c);
printf("c + 1 = %p (+1 byte)\n", (void *)(c + 1));
printf("*(p+2) = %d\n", *(p + 2));
return 0;
}$ gcc stride.c -o stride && ./stride p = 0x7ffdc0d1e2a0 p + 1 = 0x7ffdc0d1e2a4 (+4 bytes) c = 0x7ffdc0d1e2a0 c + 1 = 0x7ffdc0d1e2a1 (+1 byte) *(p+2) = 30 # same +1, different jump — the pointer TYPE sets the stride
▶ This spot has an interactive memgrid widget — open the interactive lesson to play with it.
🧠 Checkpoint: An int *p holds 0x1000 (4-byte ints). What is p + 3?
- 0x1003
- 0x100C
- 0x1004
- 0x1030
Show answer
0x100C — Three elements of 4 bytes = 12 bytes = 0xC. Pointer arithmetic always scales by the pointed-to type’s size.
Subtracting pointers
The reverse also works: subtracting two pointers into the same array gives the number of elements between them (not bytes!). The result has the signed type ptrdiff_t from <stddef.h>, printed with %td:
#include <stdio.h>
#include <stddef.h>
int main(void) {
int a[8] = {1, 2, 3, 4, 5, 6, 7, 8};
int *first = &a[1];
int *last = &a[6];
ptrdiff_t n = last - first; /* ELEMENTS, not bytes */
printf("last - first = %td\n", n);
printf("first < last : %s\n", first < last ? "yes" : "no");
return 0;
}$ gcc ptrdiff.c -o ptrdiff && ./ptrdiff last - first = 5 first < last : yes # 20 bytes apart, but the answer is 5 — arithmetic is element-wise
🧠 Checkpoint: With int a[10], what is &a[7] - &a[2]?
- 20
- 5
- 0x14
- Undefined behavior
Show answer
5 — Pointer subtraction within one array yields the element count between them: 7 − 2 = 5. The byte distance (20) is divided by sizeof(int) for you.
Comparing pointers & the one-past-the-end rule
Pointers into the same array can be compared with <, >, == — which enables the most idiomatic loop in C:
#include <stdio.h>
int main(void) {
int a[5] = {2, 4, 6, 8, 10};
int sum = 0;
for (int *p = a; p < a + 5; p++) /* a+5: one PAST the end */
sum += *p;
printf("sum = %d\n", sum);
return 0;
}$ gcc ptrloop.c -o ptrloop && ./ptrloop sum = 30
Look at that loop condition: p < a + 5. The pointer a + 5 points one past the last element. The standard explicitly blesses this one special address: you may form it and compare against it — you just may not dereference it. It exists precisely so loops like this have a clean stopping line.
One past the end is the cliff edge. Computing a + 6 (two past) or a - 1 is undefined behavior even if you never dereference it — the mere arithmetic is illegal. Real compilers really do exploit this to optimize, so "it worked on my machine" proves nothing.
🧠 Checkpoint: For int a[5], which of these is fully legal?
- Dereferencing
a + 5 - Forming and comparing the pointer
a + 5 - Computing
a + 6as long as you never dereference it - Computing
a - 1
Show answer
Forming and comparing the pointer a + 5 — The one-past-the-end pointer is the single blessed exception: form it, compare it, never read through it. Anything beyond that — even just computing the address — is undefined behavior.
▶ This spot has an interactive editor widget — open the interactive lesson to play with it.
Pointer arithmetic on element after element probably smells like something familiar — time to meet arrays properly.
▶ Practice this lesson interactively (with live gcc)