Skip to content

Step 1 of 7 · Reading · ~4 min

Learn

Pointers and Memory

Pointers in Depth: Arrays, Arithmetic and Ranges

In Fundamentals you took an address with &, followed it with *, and passed an int* to change a caller's variable. Here a pointer is a position inside a run of objects. By the end you can write functions over a pointer range, subtract pointers to get a distance, and choose the right const for a pointer.

An array decays to a pointer

Pass an array to a function and it turns into a pointer to its first element, called decay. The length is lost:

std::size_t countOf(int a[]) { return sizeof(a) / sizeof(a[0]); }   // a is really int*

int temps[5] = {18, -4, 7, 25, 7};
sizeof(temps) / sizeof(temps[0])   // 5   (20 bytes / 4)
countOf(temps)                     // 2   (8-byte pointer / 4)

Clang warns sizeof on array function parameter will return size of 'int *'. Pass the end along with the start.

Arithmetic counts elements, not bytes

p + k moves k elements; the compiler scales by sizeof(*p). Subtracting two pointers into the same array gives the element distance, of type std::ptrdiff_t (a signed 8-byte integer here):

const int* p = temps;
*(p + 3)         // 25, the same as temps[3]
(p + 3) - temps  // 3, not 12

temps[i] is defined as *(temps + i). Pointers into different arrays cannot be meaningfully subtracted or compared.

A range is [first, last)

You know begin()/end() for a vector. A raw array has the same pair: std::begin(temps) is temps, and std::end(temps) is temps + 5, one past the last element. You may form and compare that pointer, never dereference it.

long long sumRange(const int* first, const int* last) {
    long long total = 0;
    for (const int* p = first; p != last; ++p) total += *p;
    return total;
}
sumRange(temps, std::end(temps))   // 53
sumRange(temps, temps)             // 0, an empty range is first == last
sumRange(temps + 1, temps + 4)     // 28, any slice works

last - first is the element count. A search function reports "absent" with nullptr or with last, the STL way; test for whichever you chose, since *last is out of range.

Four ways to write const

Read the declaration right to left:

const int* p1 = &a;        // pointer to const int: p1 = &b is fine, *p1 = 5 is an error
int* const p2 = &a;        // const pointer to int: *p2 = 5 is fine, p2 = &b is an error
const int* const p3 = &a;  // neither may change
int* p4 = &a;              // both may change

Clang reports read-only variable is not assignable for *p1 = 5 and cannot assign to variable 'p2' with const-qualified type 'int *const' for p2 = &b. A function that only reads takes const int*, which accepts const and non-const arrays alike; int* q = constArray; does not compile.

-> reaches a member through a pointer

struct Reading { int celsius; int hour; };
Reading r{21, 14};
Reading* rp = &r;
rp->celsius      // 21, shorthand for (*rp).celsius

*rp.celsius does not compile: . binds tighter than *, so it tries rp.celsius first.

Dangling: the storage moved or died

Two cases, both undefined behaviour, so they are described rather than run:

  • Stack: int* f() { int local[3] = {1, 2, 3}; return local; } returns the address of a dead stack frame. Reading it may print old values, garbage, or crash.
  • Reallocation: with std::vector<int> v = {1, 2, 3}; and int* p = &v[0];, a v.push_back(4); grows the capacity from 3 to 6, moves every element to a new buffer and frees the old one. p keeps the old address. Take the pointer again after any call that can grow the vector.

Common mistakes

  • > where you need the last tie. For 3 9 2 9, *p > *best keeps index 1, but the last occurrence is index 3.
  • Swapping across the whole range. Every pair is swapped twice, so the array comes back unchanged and 1 2 3 prints 1 2 3. Stop at the middle.
  • Stopping two converging pointers with !=. On an even-length range they step past each other and never compare equal, so the loop runs off the array. Use <.
  • Printing *p for a position. p - a is the position; *p is the value stored there.

Your exercise

Write lastMax and reverseRange over a [first, last) range, and compute the position of the maximum by pointer subtraction in main. The starter reads the input and prints the result; the usual bug is picking the first maximum instead of the last.

Up nextDynamic Memory: new, delete and Object LifetimePointers and Memory

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…