Null Pointers and Safe Access
count_x walks a zero-terminated array of char by moving a pointer forward with ++p and comparing each element with x.
Counting Characters Without an Index
A pointer can be moved from one element of an array to the next, so a loop can visit every element of an array without using an index. This article studies a short function, count_x, that counts how many times a character x appears in a character sequence. The function is short enough to read in one pass, but the loop at its center is exactly where many learners go wrong.
What do you think happens?
Suppose the characters are b, e, e followed by the zero terminator, and x is 'e'. How many times does a correct count_x loop report x? Make a guess before you read on.
Reveal answer
2
The loop visits the characters b, e, e and then reaches the zero terminator, which ends the walk. Two of the three characters before the terminator match 'e', so the count is 2.
How a Pointer Walks Along an Array
An array stores its elements one after another, so the elements sit side by side in memory. A pointer that points to the first element can be moved forward so that it points to the next element. Each step forward moves the pointer past one element, and reading the value the pointer points to gives the element at that position. The loop in count_x uses this movement to visit each character in turn, from the first one to the last one.
The ++p step moves the pointer forward by one element of its target type. For a pointer to char, one element is one character. The walk is therefore a sequence of positions: first the pointer refers to the element at index 0, then index 1, then index 2, and so on. The diagram below shows the elements of a short zero-terminated array and the pointer positions that a forward walk visits.
Read the diagram from left to right. Each arrow is one ++p. The final element, the zero terminator, is the end marker of the array. It is not a letter of the text, but it tells any reader of the array where the text stops.
Tracing the Counting Loop Step by Step
The corrected version of count_x below is written for this article. The first line handles the null pointer. The loop header starts with an empty initializer, so the pointer keeps the value it was given. Each pass checks the element the pointer refers to, adds one to the count when it matches x, and then moves the pointer forward with ++p.
count_x("bee", 'e') returns 2- if (p == nullptr) return 0; handles the null pointer before anything is read
- for (; *p != '\0'; ++p) stops when the element is the zero terminator
- if (*p == x) ++count; adds one for each matching character
Tracing count_x on the input bee with x equal to e
Array contents: b, e, e, then the zero terminator. Call count_x with x equal to 'e' and track p, the character it refers to, and the count after each pass.
- 1Start
p refers to index 0, which holds 'b'. The null check passes because p is not null. count is 0.
- 2Pass 1
The element is 'b', which does not match 'e'. The count stays at 0. ++p moves p to index 1.
- 3Pass 2
The element is 'e', which matches. count becomes 1. ++p moves p to index 2.
- 4Pass 3
The element is 'e', which matches. count becomes 2. ++p moves p to index 3.
- 5Check
p now refers to the zero terminator, so *p != '\0' is false and the loop stops. The count is 2.
count_x returns 2, which matches the number of e characters in the array.
Two Separate Jobs: The Null Check and the Terminator
The function accepts a pointer that may hold the null value, which means the pointer points to no array at all. A function that accepts such a pointer must handle that case before it reads anything, and count_x does this on its first line with if (p == nullptr) return 0;. If there is no array, the function returns zero right away.
The null check is a different job from stopping at the terminator. The null check asks whether the pointer refers to anything. The terminator check asks whether the element the pointer refers to is the end marker of the array. These two checks look at different things, and they are easy to confuse because both involve a value that means nothing.
Call count_x with a null pointer. The first line returns 0 before the loop starts, so nothing is read. Now call it with a valid pointer to bee. The null check passes, and the loop runs until *p equals '\0'. The two cases follow different paths, and the null check is what protects the first one.
Where the Loop Must Stop
The loop as it appears in the source has the header for (; p != nullptr; ++p). This loop condition tests only whether the pointer itself is null. It does not look at the value the pointer refers to. Because of this, it does not stop at the zero terminator. Tracing the source loop on bee would reach the terminator at index 3 and keep going, moving the pointer past the end of the array into memory that is not part of the array.
for (; p != nullptr; ++p) {
if (*p == x) ++count;
}The condition tests the pointer. It stops only when p becomes null, which never happens at the terminator, so the loop reads past the end of the array.
for (; *p != '\0'; ++p) {
if (*p == x) ++count;
}The condition tests the element. It stops when the element is the zero terminator, which matches the array's end marker.
The result can diverge from what you expected at the pass where p reaches the terminator. In the source loop, the walk continues after that pass. Whatever the loop then reads depends on memory the array does not own, so the count is not guaranteed to be 2. The mistake is a stopping condition that does not match the array's end marker, not the null check.
Mistakes Beginners Make with the Loop
The following mistakes come up when learners read or write a pointer-based counting loop. Each one is tied to the difference between checking the pointer and checking the element it refers to.
Common mistake Treating the null check as the loop's stopping test
What it looks likefor (; p != nullptr; ++p) with a valid arrayWhy it failsp is never null while it walks through a valid array, so the loop does not stop at the zero terminator.
Do this insteadTest the element the pointer refers to, for example *p != '\0'.
Common mistake Dereferencing a null pointer before checking it
What it looks likeif (*p == x) before if (p == nullptr) return 0;Why it failsReading through a null pointer points to no array, so there is nothing valid to read.
Do this insteadKeep if (p == nullptr) return 0; as the first line, before any read.
Common mistake Assuming the null pointer and the zero character are interchangeable
Why it failsA null pointer is a pointer with no target, while '\0' is a character value stored in the array.
Do this insteadUse nullptr to check the pointer and '\0' to check the end of the text.
Common mistake Forgetting that ++p moves by one element, so the count is off when the walk is started at the wrong position
Practice Tracing a Pointer Loop
Use the corrected count_x. Trace the call count_x("aa", 'a') and write down p, the character it refers to, and the count on each pass. Then say which pass stops the loop and why. Finally, explain what would happen to the count if the loop condition were p != nullptr instead.
Hints
- Count the elements before the zero terminator: there are two a characters.
- The loop stops on the pass where *p is '\0', not on the pass where p is null.
Key Takeaways
- A pointer walks a zero-terminated array by moving forward with ++p, one element of its target type at a time.
- A null check such as if (p == nullptr) return 0; handles the case where the pointer points to no array, and it runs before any element is read.
- The loop must stop on the zero terminator by testing the element, such as *p != '\0', because a test on p alone never stops at the end marker.
- A null pointer and the '\0' character are different: one is a pointer with no target, the other is the end marker stored in the array.