C++ / a working model

32 / 163   ·   C++11   ·   8 min

Pointers: addresses, bounds, and borrowing

Keep this sentence

A pointer is a typed value that may point to an object, a function, or a past-the-end position, or it may be null or invalid. Non-null is not proof that you may dereference. Correct use depends on the object still being alive, matching types, and access staying in bounds. A raw pointer itself also does not express deallocation responsibility.

In this lesson
  1. A pointer is itself an object
  2. Pointer arithmetic is defined around arrays
  3. Interfaces must supply what a pointer lacks
  4. Example
  5. Exercise

A pointer is itself an object

int* p = &value creates a pointer object that stores a pointer value to value. Assigning a new address to p changes the pointer; assigning to *p changes the pointed-to integer. Copying a pointer only adds another access path; it does not copy the integer or extend the integer's lifetime.

nullptr expresses that there is no target and can be used for optional borrowing. Checking non-null before dereference only rules out a null value; it cannot rule out a destroyed object, an unsuitable type, or an invalidated range. The standard does not require a null pointer's object representation to be all-zero bytes, so initialize or assign rather than zero the pointer storage byte by byte.

Pointer arithmetic is defined around arrays

Pointers in the same array may move to an element or to the past-the-end position. A past-the-end pointer is only for bound checks; you cannot read the element it points to. p + 1 steps by element, not by adding one byte to the address. A non-array object may be treated as an array of length one under these arithmetic rules.

The example sums a half-open range [first, last) and stops before the past-the-end position. Subtracting two pointers requires that they belong to the same array range and that the difference is representable in the corresponding type. Distinct local variables must not be traversed as an array even if they happen to be adjacent; observing layout cannot replace language guarantees.

Interfaces must supply what a pointer lacks

A single pointer does not carry element count or ownership. An interface that needs a range should also give a length, or in C++20 use std::span to keep address and length together. A span is still a borrow; it does not keep the observed data alive, and the caller must ensure the underlying objects live long enough.

Raw pointers suit nullable non-owning access. Resource owners should use containers or smart pointers. In the example the array is managed by the current scope; first and last are used only while the array is alive, so you neither need nor must ever delete them. Identifying deallocation responsibility matters more than memorizing address numbers.

Pitfalls

  • Checking only p != nullptr cannot detect dangling; a freed pointer usually does not become null automatically.
  • Forming a pointer more than one past the end of an array can already violate the rules; it is not only out-of-bounds dereference that is a problem.

Run an example

Minimum C++11 · complete program · Download .cpp

#include <cassert>

int main() {
    int values[]{2, 4, 6};
    int* first = values;
    int* last = values + 3;
    int sum = 0;
    for (int* p = first; p != last; ++p) {
        sum += *p;
    }
    assert(sum == 12);
    assert(last - first == 3);
    int* optional = nullptr;
    assert(optional == nullptr);
    optional = &values[1];
    *optional = 9;
    assert(values[1] == 9);
}

Compile locally

g++ -std=c++11 -Wall -Wextra -Wpedantic -pthread memory-pointers.cpp -o example && ./example

Expected result

Expected: exit 0, no output; every assert holds.

CHECK YOUR UNDERSTANDING

Close the answer. Explain it.

For values of length three, may values + 3 be stored? May it be read? What about values + 4?

Show a reference answer

values + 3 is a valid past-the-end pointer; it may be stored and compared with a traversal pointer, but it must not be dereferenced to read. values + 4 is outside the array range that may be formed and should not be computed. If you need a logically farther position, first range-check with an integer index and form the pointer only after it is known to be valid.

Check the sources

Drafts and official chapters change. The version mark is only the example’s minimum.

Back to the catalog