Ch5.6: Pointer Arithmetic

Overview

Pointer arithmetic means adding or subtracting an integer from a pointer. This moves the pointer forward or backward by a number of elements, not bytes. Pointer arithmetic only makes sense when applied to pointers that refer to elements of the same C‑style array.

In this chapter, you will learn:

1. Pointer arithmetic moves by elements, not bytes

When you add 1 to a pointer, it moves to the next element of its type.


int a[3]{10, 20, 30};

int *p = ::std::addressof(a[0]);
int *q = p + 1;   // moves to a[1]

Example addresses:

a[0] at 1000
a[1] at 1004
a[2] at 1008

p = 1000
p + 1 = 1004   ← moved by sizeof(int)

Pointer arithmetic automatically multiplies by sizeof(T).

2. Subtracting pointers

Subtracting two pointers to elements of the same array gives the number of elements between them.


int a[3]{10, 20, 30};

int *p = ::std::addressof(a[0]);
int *q = ::std::addressof(a[2]);

::std::ptrdiff_t diff = q - p;   // diff == 2

The result is measured in elements, not bytes.

2.1 ::std::ptrdiff_t — the type of pointer differences

When you subtract two pointers, the result is stored in a type called ::std::ptrdiff_t. This is a signed integer type defined by the C++ standard specifically for representing the distance between two pointers.


int a[3]{10, 20, 30};

int *p = ::std::addressof(a[0]);
int *q = ::std::addressof(a[2]);

::std::ptrdiff_t diff = q - p;   // diff == 2

::std::ptrdiff_t is always a signed type, because pointer differences may be negative.

fast_io guarantee

All contiguous containers in fast_io — including ::fast_io::vector, ::fast_io::array, and ::fast_io::string — guarantee that:

This matches the C++ standard library and avoids subtle bugs involving signed/unsigned mismatches.

3. Pointer arithmetic only works inside the same array

Pointer arithmetic is only valid when the pointer points:

Anything else is undefined behavior.


int x{5};
int *p = ::std::addressof(x);

// p + 1;   // undefined behavior — not part of an array

4. One‑past‑the‑end pointer

C++ allows a pointer to point one past the last element of an array. But you must never dereference it.


int a[3]{10, 20, 30};

int *first = ::std::addressof(a[0]);
int *end   = first + 3;   // one past the end (valid)

// *end;   // undefined behavior — cannot dereference

This pointer exists only so you can compare it or move backwards.

5. Dereferencing out‑of‑bounds pointers is undefined behavior

Pointer arithmetic that produces an out‑of‑bounds pointer is undefined behavior. Dereferencing such a pointer is also undefined behavior.


int a[3]{10, 20, 30};

int *p = ::std::addressof(a[0]);

int *bad = p + 10;   // undefined behavior — out of bounds
// *bad;             // undefined behavior

Undefined behavior means:

You cannot rely on any specific outcome.

WebAssembly note

On WebAssembly without memory tagging, out‑of‑bounds pointer arithmetic often does not trap, making bugs harder to detect.

6. Undefined behavior does NOT guarantee a crash

Many beginners assume that invalid pointer arithmetic will crash immediately. This is false.

Compilers may assume:

This can cause the compiler to optimize away checks or reorder code in ways that make debugging extremely difficult.

Undefined behavior is dangerous precisely because it is unpredictable.

7. Pointers are the iterators of C‑style arrays

In modern C++, pointers are considered the highest‑level iterators. They satisfy all iterator requirements and are used as the iterators for C‑style arrays.

For an array a, the iterators are simply:


int a[3]{10, 20, 30};

int *first = ::std::addressof(a[0]);
int *last  = first + 3;   // one past the end

These pointers behave exactly like iterators in the C++ ranges library.

8. Using ::std::ranges::begin(a) and ::std::ranges::end(a)

Modern C++ provides a uniform way to obtain iterators for arrays:


#include <ranges>

int a[3]{10, 20, 30};

int *first = ::std::ranges::begin(a);
int *last  = ::std::ranges::end(a);

These functions return:

This is the preferred modern way to obtain iterators for C‑style arrays.

9. Invalid pointer arithmetic

C++ allows only a very small set of arithmetic operations on pointers. Anything outside these rules is a compile‑time error or undefined behavior.

9.1 Allowed operations

9.2 Forbidden operations


int a[3]{10, 20, 30};
int *p = a;
int *q = a + 2;

// p + q;        // ❌ illegal
// p * 2;        // ❌ illegal
// p / 2;        // ❌ illegal
// (p + q) / 2;  // ❌ illegal

int *mid = p + (q - p) / 2;   // ✔ correct

This works because q - p is a number of elements, not a pointer.

10. Iterator arithmetic follows the same rules

The same restrictions on pointer arithmetic also apply to iterators of ::fast_io::array, ::fast_io::vector, and ::fast_io::string. All three containers provide contiguous iterators, meaning their iterators behave exactly like raw pointers.

Allowed operations:

Forbidden operations:


::fast_io::vector<int> v{10, 20, 30, 40};

auto it1 = v.begin();
auto it2 = v.begin() + 3;

// it1 + it2;        // ❌ illegal
// it1 * 2;          // ❌ illegal
// (it1 + it2) / 2;  // ❌ illegal

auto mid = it1 + (it2 - it1) / 2;   // ✔ correct

This works because it2 - it1 is a number of elements.

Why this is true

Contiguous iterators are required by the C++ standard to behave exactly like pointers for arithmetic. Since ::fast_io::array, ::fast_io::vector, and ::fast_io::string all store elements contiguously, their iterators follow the same rules as raw pointers.

Key takeaways