Pointer arithmetic, arrays, and safe interfaces
Understand pointer arithmetic within arrays, one-past-end pointers, and safer span-based views.
Learning objectives
- Explain the relationship between arrays and pointers in common expressions.
- Use pointer arithmetic only within one array object.
- Understand the one-past-the-end pointer rule.
- Recognize std::span as a non-owning view in C++20.
Prerequisites: understand pointers, arrays, pointer arithmetic, and object lifetime. Review Pointers, Arrays, and Scope and lifetime first.
Array elements and addresses
For an array, the expression array is commonly converted to a pointer to its first element in many expression contexts. A pointer to an element can be advanced to later elements using pointer arithmetic, but this arithmetic is constrained to the same array object and its one-past-the-end position.
int values[3]{10, 20, 30};
int* first = values;
std::cout << *first << '\n'; // 10
std::cout << *(first + 1) << '\n'; // 20
int* end = values + 3; // one past last elementThe one-past-the-end pointer may be formed and compared, but must not be dereferenced. It is useful as an exclusive end marker for a range.
Do not treat memory as an unrestricted number line
Pointer arithmetic is not permission to move to arbitrary addresses. Adding an offset outside the same array object (or one-past position) is not defined by the language. Dereferencing a pointer that does not designate a live object is invalid.
Pointer and length pairs
Low-level interfaces sometimes accept a pointer and a separate length. The caller must keep them consistent. A mismatch can cause out-of-bounds access. In C++20, std::span packages a non-owning view of a contiguous sequence with its size, making this relationship clearer.
#include <span>
void process(std::span<const int> values) {
for (int value : values) {
// read each element
}
}A span does not own the data. The underlying array or vector must remain alive while the span is used.
Common mistakes
- Dereferencing the one-past-end pointer.
- Adding arbitrary offsets to a pointer and assuming all memory is one array.
- Passing a pointer without a trustworthy length.
- Keeping a span after the original storage has been destroyed or invalidated.
Practice
- Print all elements of an array using a pointer and an end pointer.
- Explain why
values + 3is valid for a three-element array but*(values + 3)is not. - Write a C++20 function that accepts
std::span<const int>. - Explain why span is non-owning.
Practice solutions
Show worked solutions
int values[]{4, 8, 12}; int* first = values; int* last = values + 3; // one past the final element for (int* current = first; current != last; ++current) { std::cout << *current << '\n'; }The loop dereferences only pointers to actual elements.values + 3is the permitted one-past-the-end pointer for a three-element array. It may be compared or used as an end marker, but dereferencing it does not access an element and is undefined behavior.#include <span> #include <iostream> void print_values(std::span<const int> values) { for (int value : values) std::cout << value << '\n'; }This interface requires C++20.- A span stores a view (typically a pointer and a length), not the elements. The caller must ensure the underlying range remains alive and valid for the span's use.
Check your understanding
Can the one-past-end pointer be dereferenced?
Answer: No. It can be used as an end marker but does not point to an element.
Summary
Pointer arithmetic is limited to an array object and its one-past-end position. Prefer interfaces that carry size information, such as C++20 span, when a non-owning contiguous view is appropriate.