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:
- how pointer arithmetic works
- why pointer arithmetic moves by elements, not bytes
- how pointer subtraction works
- what
::std::ptrdiff_tis - why pointer arithmetic is only valid within the same array
- why out‑of‑bounds arithmetic is undefined behavior
- why undefined behavior does not guarantee a crash
- that pointers are the iterators of C‑style arrays
- how
::std::ranges::begin(a)and::std::ranges::end(a)work - what pointer arithmetic is not allowed
- why the same rules apply to contiguous iterators of
::fast_io::array,::fast_io::vector, and::fast_io::string
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:
- their iterator difference type is
::std::ptrdiff_t - their
.size()returns::std::size_t
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:
- to an element of an array, or
- to one past the last element
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:
- the program might crash
- or corrupt memory
- or silently produce wrong results
- or appear to work
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:
- pointers always point to valid objects
- pointer arithmetic stays within array bounds
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:
::std::addressof(a[0])forbegin(a)::std::addressof(a[0]) + 3forend(a)
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
ptr + nptr - n++ptr,--ptrptr += n,ptr -= nptr2 - ptr1(distance in elements)
9.2 Forbidden operations
ptr + ptrptr * nptr / nptr % nptr1 * ptr2ptr1 / ptr2(ptr1 + ptr2) / 2
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:
it + nit - n++it,--itit2 - it1
Forbidden operations:
it + itit * nit / n(it1 + it2) / 2
::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
- Pointer arithmetic moves by elements, not bytes.
- Pointer subtraction yields a
::std::ptrdiff_t. - fast_io containers use
::std::ptrdiff_tfor iterator differences and::std::size_tfor sizes. - Pointer arithmetic is only valid within the same array.
- You may form a pointer one past the end, but never dereference it.
- Out‑of‑bounds pointer arithmetic is undefined behavior.
- Undefined behavior does NOT guarantee a crash.
- Pointers are the iterators of C‑style arrays.
- Use
::std::ranges::begin(a)and::std::ranges::end(a)for array iterators. - Operations like
ptr + ptr,ptr * n, and(ptr1 + ptr2) / 2are illegal. - Contiguous iterators of
array,vector, andstringfollow the same rules as pointers.