The C Path — learn C, visually

🎯 Pointers & Memory

Pointer arithmetic: +1 is not one byte

⏱ 12 min · free interactive lesson · quizzes, visualizations & a real compiler

▶ Open the interactive lesson — free, no signup
Why you're learning this

Any 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.

stride.c
#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;
}
terminal
$ 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:

ptrdiff.c
#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;
}
terminal
$ 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:

ptrloop.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;
}
terminal
$ 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 + 6 as 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)