C++ PROGRAMMING ยท 14 MIN STUDY

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 element

The 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

  1. Print all elements of an array using a pointer and an end pointer.
  2. Explain why values + 3 is valid for a three-element array but *(values + 3) is not.
  3. Write a C++20 function that accepts std::span<const int>.
  4. Explain why span is non-owning.

Practice solutions

Show worked solutions
  1. 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.
  2. values + 3 is 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.
  3. #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.
  4. 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.

References