Using Arrays and Pointers in Loops
Many programmers who write loops over arrays begin with an index variable: a counter that runs from 0 upward and is used to read each element.
A Loop Without an Index
Many programmers who write loops over arrays begin with an index variable: a counter that runs from 0 upward and is used to read each element. Pointers offer another way to walk an array. Instead of tracking a number, the loop keeps a pointer to the current element and moves that pointer forward until the work is done. Once you see how the pointer moves, the loop no longer needs an index at all.
Programmers who arrive at pointer loops often carry a shortcut with them. The shortcut says that a pointer which has run off the end of its data is empty, and that 0 or NULL can be written to say so. The shortcut feels harmless, and older code uses it everywhere. This article starts from that belief, checks it against how pointers behave inside loops, and then shows a clearer way to write the same loops.
What do you think happens?
Before reading on: if a pointer and a loop are used to count the characters in a string, what should the loop compare against to know it has reached the end of the text?
Reveal answer
The character the pointer refers to, compared with the zero terminator
A C-style string carries its own end marker, the zero terminator, so a pointer-walking loop stops when it reaches that character. The pointer's null state is a different thing and does not mark the end of the text. The next sections explain why.
Stepping a Pointer Through an Array
The increment operator ++ is familiar from integer variables, where it adds 1. Applied to a pointer that refers to an element of an array, ++ does something different and more useful: it moves the pointer to the next element of that array. Each step of the loop advances the pointer one element, and the code reads whichever element the pointer currently refers to.
Trace the array below. The pointer p starts at the first element, a[0]. After one ++ it refers to a[1]. After another ++ it refers to a[2]. No index variable appears anywhere in this process. The position of the pointer is the only record of where the walk currently is.
10
20
30- int* p = a; sets the pointer to the first element without an index
- *p reads the element the pointer currently refers to
- ++p moves the pointer to the next element of the array
C-Strings and the Zero Terminator
A C-style string is a zero-terminated array of char. The characters of the text come first, and a special character with the value zero, written '\0', follows them to mark where the data ends. The string does not carry a separate length. A function that works on a C-style string finds the end by walking forward until it reaches the zero terminator.
Read the cells from left to right. A pointer that starts at the first cell and is incremented once refers to the second cell. Incrementing again moves it onto the terminator. The loop does not need to know the number of characters before it begins. It only needs to know the rule for when to stop, which is to stop when the character under the pointer is the zero terminator.
Walking a String Until the Terminator
A function that counts characters does not receive a length from its caller. It walks the pointer forward and counts steps until the zero terminator appears. The definition only works when the input really is a valid C-style string, so the caller is responsible for providing one.
2
0- 1The pointer p starts at the first character of the string passed in.
- 2The condition checks two things: that p refers to something, and that the character under it is not the zero terminator.
- 3When both hold, the body adds 1 to n and moves p to the next character.
- 4When the terminator is reached, the condition is false and the loop ends with n holding the count.
- p != nullptr guards against a missing input and is tested once, in the loop condition
- *p != '\0' checks the character under the pointer against the zero terminator
- ++p advances the pointer so the loop reads the next character on the following pass
- Passing nullptr returns 0 without reading any character
Tracing count_chars on "hi"
What value does count_chars return when it is given the C-style string "hi"?
- 1Start
p refers to 'h'. The condition is true because p is not null and 'h' is not the zero terminator. n becomes 1 and p moves to 'i'.
- 2Second pass
p refers to 'i'. The condition is true. n becomes 2 and p moves to the terminator.
- 3Third test
p refers to '\0'. The condition's second part is false, so the loop stops.
The function returns 2.
Why nullptr Replaces 0 and NULL
The belief to examine is that writing 0 or NULL in a pointer test means exactly the same thing as writing nullptr. In a narrow sense the program may run as expected, but the reasons for preferring nullptr are about clarity. nullptr is the null pointer literal. It states plainly that the value is a pointer with no target, while 0 is an integer that happens to be used in pointer contexts.
The difference matters because integers and pointers can be confused. When code uses 0 for a pointer, a reader or a compiler must infer the intent from context. Using nullptr removes that ambiguity, and it also keeps the code correct when the same name is used in places where an integer and a pointer could both apply, such as when a function is overloaded to accept either kind of argument.
| Spelling | What it is | What a reader learns from it |
|---|---|---|
| nullptr | The null pointer literal | This value is a pointer with no target |
| 0 | An integer value | Could be an integer or a pointer, depending on context |
| NULL | A macro that expands to a null pointer constant in older code | Pointer intent is implied rather than stated in the language |
Three ways to write a null pointer test
Write nullptr when you test or assign a pointer. Keep 0 for integer values. This makes every pointer test state its intent, so later readers do not need to decide whether a zero was meant as a number or as an empty pointer.
Rewriting a Two-Check Loop into One
A first version of a character-counting function often tests for nullptr in two places: once before the loop, to handle a missing input, and again inside the loop, to decide whether to keep going. The second test adds nothing once the loop is built around the pointer's value. Because the loop advances the pointer along the array, the loop condition can carry the whole job of deciding when to stop.
int count_chars(const char* p) {
if (p == nullptr) return 0;
int n = 0;
while (*p != '\0') {
if (p == nullptr) break;
++n;
++p;
}
return n;
}The null test appears before the loop and again inside the body, so a reader must check two places to know when the loop ends.
int count_chars(const char* p) {
int n = 0;
while (p != nullptr && *p != '\0') {
++n;
++p;
}
return n;
}The null test sits in the single loop condition, next to the terminator test, so the stopping rule is in one place.
The two versions return the same results for valid input. The rewritten version is easier to check, because a reader looks at one condition to learn when the loop stops. Since the while-statement repeats its body until its condition becomes false, it fits this loop directly. A for-statement could express the same logic, but its header would have empty parts because the pointer is set up before the loop begins. The while form states that directly.
Common Mistakes with Pointer Loops
Common mistake Stopping a string loop on a null pointer instead of the zero terminator
What it looks likewhile (p != nullptr) { ++n; ++p; } on "hi"Why it failsThe pointer is not null when it reaches the end of the text. It still refers to the terminator, so the loop keeps going and reads past the string.
Do this insteadStop when the character under the pointer is '\0', as in while (*p != '\0').
Common mistake Using 0 or NULL where nullptr is clearer
What it looks likewhile (p != 0) in a function that takes a pointerWhy it failsThe 0 is an integer, and the reader must infer that it was meant as a pointer. Integers and pointers can be confused, which hides the intent.
Do this insteadWrite while (p != nullptr) so the test states that the value is a pointer.
Common mistake Testing for null in two places when one condition is enough
What it looks likeA null check before the loop and another inside the body that never changes the result
Why it failsThe second check repeats a decision the loop condition already makes, so a reader has to verify both places.
Do this insteadMove the pointer test into the single loop condition.
Common mistake Dereferencing the pointer after it has moved past the terminator
What it looks likeIncrementing p once more after reading the '\0' and then reading *p
Why it failsThe characters past the terminator are not part of the string, so reading them is not a valid use of the data.
Do this insteadCheck the character before advancing, and stop as soon as it is the terminator.
When a pointer loop returns a surprising result, the fault is usually at one of three points: the start of the walk, the test that decides whether to continue, or the step that moves the pointer. Ask what the pointer refers to at each test and whether that is the value you expected. This is faster than rereading the whole function.
Practice: Trace a Pointer Walk
Using the count_chars function above, trace the call count_chars("abc"). List the character the pointer refers to on each test, and state the value returned.
Hints
- Write down p and the character it refers to before each condition check.
- The loop stops on the first test where the character is the zero terminator.
- Count how many times the body runs before that test.
Rewrite a function that takes a pointer to a C-style string and prints each character, but which currently tests for nullptr both before the loop and inside it. Make it test in one place only.
Hints
- Keep the nullptr check in the while condition, combined with the terminator check using &&.
- Remove the second check from the loop body.
Key Takeaways
- A pointer moves to the next element of an array with ++, so a loop can walk the array without an index variable.
- A C-style string is a zero-terminated array of char; the zero terminator, not a null pointer, tells the loop where the data ends.
- Use nullptr for pointer tests and 0 for integers, because nullptr states the intent and avoids confusion between integers and pointers.
- Testing a pointer in a while condition is equivalent to comparing it with nullptr, so a pointer loop can test for null in one place.