C Mastery / Pointers Part 2: Arithmetic, Comparison, and One-Past-the-End
Part 1 — The Core Language

Pointers Part 2: Arithmetic, Comparison, and One-Past-the-End

This chapter covers pointer arithmetic, pointer comparison, and the special one-past-the-end pointer. These rules make C arrays and pointer iteration work, and their boundary conditions are a major source of undefined behavior.

Why This Matters

Pointer arithmetic is how you traverse arrays, implement data structures, and read memory in systems code. Its rules are stricter than "pointers are numbers" suggests, and violating them is undefined behavior even when the arithmetic "looks fine."

Prerequisites

Core Concept

Pointer arithmetic

For a pointer p of type T * and an integer n, p + n points to the object n elements away, where "one element" is sizeof(T) bytes. So p + 1 adds sizeof(T) bytes to the address, not 1 byte.

int *p = &arr[0];
int *q = p + 2;   /* q points to arr[2], sizeof(int) bytes further */

Subtraction of a pointer and an integer (p - n) is the reverse. Subtraction of two pointers (q - p) yields the number of elements between them, as a ptrdiff_t, and is defined only when both pointers point into the same array object (or one past the end).

Pointer comparison

Two pointers can be compared with ==, !=, <, >, <=, >=. Equality and inequality are well-defined for valid pointers (including comparing against NULL). Relational comparison (<, etc.) is defined when both pointers point into the same array object (or one past the end); otherwise it is unspecified in some cases (see below).

One-past-the-end

A pointer may legally point one element past the end of an array object. Such a pointer may be compared and used in arithmetic, but it must not be dereferenced.

int arr[5];
int *end = arr + 5;   /* valid one-past-the-end pointer */
/* *end is UNDEFINED BEHAVIOR */

Syntax

p + n;
p - n;
p - q;         /* ptrdiff_t */
p == q;  p != q;
p < q;  p > q;  p <= q;  p >= q;

Examples

Iterating with pointer arithmetic

#include <stdio.h>

int main(void)
{
    int arr[5] = {10, 20, 30, 40, 50};
    for (int *p = arr; p < arr + 5; p++) {
        printf("%d\n", *p);
    }
    return 0;
}

Expected output: 10 through 50, one per line.

Pointer subtraction

int arr[5];
int *a = &arr[1];
int *b = &arr[4];
ptrdiff_t n = b - a;   /* 3 */

One-past-the-end in a loop

int arr[5];
int *end = arr + 5;
for (int *p = arr; p != end; p++) {
    /* process *p */
}

This is the canonical "iterate to one-past-the-end" pattern.

How It Works

Pointer arithmetic is *typed*: the compiler multiplies n by sizeof(T) to compute the byte offset. This is why int *p and char *p step by different amounts. Subtraction of two pointers divides the byte difference by sizeof(T) to yield an element count.

Variations

void* arithmetic (not allowed)

You cannot do arithmetic on void * in ISO C, because void has no size. (GCC allows it as an extension, treating void as size 1, but that is not portable.) Cast to a concrete type first.

Arithmetic on char pointers

Because sizeof(char) == 1, char * arithmetic operates on individual bytes. This makes char * (or unsigned char *) the right type for raw byte access.

Common Mistakes

is not.

Undefined Behavior

one-past-the-end. VERIFIED

array object. VERIFIED

Portability

same array is unspecified (in C17; C23 refines this). Use ==/!= for arbitrary valid pointers, and relational comparison only within arrays.

Under the Hood

Pointer arithmetic compiles to address computation: for T *p, p + n computes base + n * sizeof(T). On x86-64, this is a scaled addressing mode (lea rax, [rdi + rsi*4] for int). Pointer subtraction compiles to a subtract followed by an arithmetic shift/divide by the element size.

Practical Usage

library's iterator-style functions and many container designs).

Exercises

1. Write a loop that iterates over an int array with a pointer and prints each element. 2. Demonstrate that p + 1 for int* moves by sizeof(int) bytes, and compare with char*. 3. Compute the number of elements between two pointers using subtraction. 4. Explain why arr + 5 is valid but * (arr + 5) is UB.

Deep Challenge

Write a function size_t arrlen(int *begin, int *end) that returns the number of elements in the range [begin, end), and explain why it is well-defined only when begin and end point into the same array (with end possibly one past the end). Then explain what goes wrong if they point into different arrays.

References

operators), §6.5.9 (equality operators).

Verification

verified.`

Progress

Concept checkboxes

Mastery levels

ConceptCurrent level (0–8)Target level
c.ptr.arithmetic06
c.ptr.compare05
c.ptr.onepast05