Pointer Arithmetic and Arrays
Indexing into an array (like samples[3]) is just pointer arithmetic under the hood. That single idea explains a lot: how arrays and pointers are so tightly linked in this language, and why the quirks around arrays and function parameters aren’t arbitrary at all.
This lesson shows how to navigate a C-style array with a pointer instead of an index.
The addresses behind an array
The following array is a block of memory with 5 int-sized slots.
int samples[5] = {68, 72, 75, 71, 69};If the first slot happens to start at address 1000, and an int takes up 4 bytes on your system, then the second slot starts at 1004, the third at 1008, and so on. That stride is the key to pointer arithmetic.
Adding to a pointer
As you would expect, &samples[0] gives the address of the first element of the array. If you have a pointer to that first element, you can add a number to it to get another pointer further along the array:
Here, p + 1 and p + 2 are pointers. p + 1 does not mean “add 1 to the address.” It means “move forward by the size of one element.” Since p points to an int, p + 1 skips forward 4 bytes (on most systems), landing exactly on the next int. If p pointed to a double, p + 1 would skip forward 8 bytes instead. This is managed by the compiler automatically based on the pointer’s type. Lines 8–9 show how these pointers can be dereferenced.
Pointers p and p + 2
Indexing is arithmetic in disguise
An array’s name works like a pointer to its first element, so the pointer arithmetic from the last section applies directly to it.
Concretely, samples behaves like a pointer holding the address &samples[0]. And samples + 1 gives you the address of the second element of the array. When you write samples[1], the compiler translates that into *(samples + 1), so these two lines literally do the same thing:
Both print 72. The square-bracket notation is just a friendlier way of writing pointer arithmetic followed by a dereference.
Oddly, samples[3] and 3[samples] compile to the same thing in C++. That’s because addition gives the same result for 3 + samples and samples + 3. You’d never write it that way in practice, but it shows what’s really going on underneath.
When you use just an array’s name in an expression, it is said to decay into a pointer to its first element.
Passing an array as a pointer parameter
Passing an array name to a function means you’re actually passing a pointer, which is why inspectArray can receive it in an int* parameter (line 3):
In an earlier lesson, you saw C-style arrays passed to functions like this: void inspectArray(int arr[]). In a function parameter, arr[] is just another way of writing int* arr.
Since the parameter arr is a pointer inside inspectArray, printing sizeof(arr) inside that function (line 5) gives the size of a pointer, and not the size of the original array. This explains why a separate parameter must be used for passing the size of the array, when the function needs it.
Passing strings to functions
We mentioned earlier that a string literal like "Hello" has the type const char[6]. Being an array, it behaves the same way. When you pass it to a function, it decays into a pointer to its first element, a const char* (line 8).
A char array like greeting decays the same way (line 11). You don’t need to pass the length of the string, because the null terminator ('\0') marks where it ends.
Walking an array with a pointer instead of an index
You can use a pointer to loop over an array, instead of using an index. Here’s the familiar way first, using an index to find the maximum:
int samples[7] = {68, 72, 75, 71, 69, 74, 77};int highest = samples[0];for (int i = 1; i < 7; i++) { if (samples[i] > highest) { highest = samples[i]; }}Here’s the same logic implemented with a pointer:
- In the loop on lines 10–14,
pis initialized to point to the start of the array. - In each round,
p++moves the pointer forward by one element. *pin the loop body retrieves the element to whichppoints.- The loop stops when
preachesend, i.e., the address that’s one past the last element of the arra.
Initializing end to an address beyond the array looks dangerous, but it isn’t, as long as you don’t dereference it. C++ specifically guarantees that computing a pointer to one position past the end of an array is legal, purely so you can use it as a stopping point for a loop, like p != end here.
Subtracting pointers
It’s also possible to subtract two pointers. When they are pointing into the same array, subtracting them tells you the distance between them (in terms of elements, not bytes).
Subtracting two pointers
Here, we position the start pointer at the beginning of the array, and the end pointer one past its end:
So end - start gives you the length of the array. Subtraction accounts for the element size (the number of bytes) automatically, the same way addition does.
Quick reference
| Expression | What it evaluates to |
|---|---|
&samples[0] | Address of the array’s first element |
p + 1 | A new pointer, one element ahead of p |
*(p + 1) | The value at the new pointer’s address |
samples[i] | Same as *(samples + i), the value at index i |
samples (used alone) | Decays into a pointer to the first element |
end - start | The number of steps needed to move start to end‘s position |
Checkpoint
Assuming a double takes 8 bytes and measurements[0] starts at address 2000, what address does p hold after this code runs?
double measurements[4] = {9.99, 19.99, 29.99, 39.99};
double* p = measurements;
p = p + 3;Assuming pointers are 8 bytes on this system, what does this print?
void process(int data[]) {
std::cout << sizeof(data);
}
int main() {
int data[10];
process(data);
}What does distance equal after this code runs?
int samples[6] = {10, 20, 30, 40, 50, 60};
int* p = &samples[4];
int distance = p - samples;