Pointer Arithmetic Basics
count_x walks a zero-terminated array of char by moving a pointer forward with ++p and comparing each element with x.
A Counting Function 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 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.
Before reading the explanation, look at the function below and keep a guess in mind about what the loop is doing. Ask yourself what the pointer p points to on each pass, and what has to be true for the loop to finish. Hold on to that guess, because the rest of the article tests it.
- The first line handles a pointer that points to no array at all.
- The loop header has an empty initializer, so p keeps the value it was given.
- The ++p step moves p to the next char on every pass.
- The loop condition tests only whether p itself is null.
How a Pointer Walks Through Characters
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 to point 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.
Pointer arithmetic of this kind means the pointer advances by one element of its target type. For a pointer to char, one element is one character, so ++p moves the pointer exactly one character forward. Each pass through the loop body then checks the element the pointer currently refers to and adds one to the count when it matches x.
In the diagram, the highlighted cell is the element the pointer refers to after three steps. The pointer sits three characters past the start of the array, and the character it reads there is a. The array ends with a zero value at index 6, which is the terminator. The terminator is stored in the same contiguous run as the characters, so the end marker sits directly after the last usable element.
The Zero Terminator and Its Role
A zero-terminated array marks its end with a zero value. The zero is stored as an element of the array, placed just after the last character that belongs to the sequence. Because the marker is part of the data, a loop that walks the array can detect the end by looking at the element the pointer refers to.
The stopping condition of a pointer-based loop must match the array's end marker. If the loop stops at the terminator, every real character is visited exactly once and the loop ends cleanly. If the stopping condition checks something else, the loop has no reliable point at which to end.
Tracing the Correct Counting Loop
Now follow the loop step by step. The input is the zero-terminated array holding banana, and x is the character a. The correct loop stops when the pointer reaches the terminator. The table below records the pointer, the element it reads, and the count after each pass.
Counting a in banana with a loop that stops at the terminator
Count how many times x = 'a' appears in the zero-terminated array b a n a n a followed by the terminator.
- 1Start, p at index 0
p reads 'b'. That is not 'a', so count stays 0. The element is not the terminator, so the loop continues.
- 2After one ++p, p at index 1
p reads 'a'. It matches x, so count becomes 1.
- 3After two ++p, p at index 2
p reads 'n'. No match, count stays 1.
- 4After three ++p, p at index 3
p reads 'a'. It matches, so count becomes 2.
- 5After four ++p, p at index 4
p reads 'n'. No match, count stays 2.
- 6After five ++p, p at index 5
p reads 'a'. It matches, so count becomes 3.
- 7After six ++p, p at index 6
p reads the zero terminator. The stopping condition is met, so the loop ends and the function returns 3.
The function returns 3, which matches the three occurrences of a in banana.
The count changes only on the three passes where the pointer reads a. The loop stops on the seventh position, which holds the terminator, and that is the point where the array's end marker calls for the loop to stop. The result of 3 is correct because the loop visited every real character and then stopped at the marker.
Null Pointer Check Before Any Read
The function accepts a pointer that may hold the null value, which means it points to no array at all. A null pointer is a pointer that points to nothing. Using it to read a character would be an error, so the function checks for it before doing anything else. The first line, if (p == nullptr) return 0;, returns zero right away when there is no array.
Predict first: if count_x is called with a null pointer, does the loop body ever run? The first line returns 0 before the loop begins, so no element is read. The null check is a separate job from stopping at the terminator. It answers the question of whether there is any array at all, while the terminator check answers where the array ends.
The flowchart separates the two jobs. The null check runs once, before any element is read. The terminator check runs on every pass and decides when the walk ends. A corrected loop combines the pointer test and the terminator test in its condition, as in the version below, which is a generated correction of the source loop.
count_x("banana", 'a') returns 3- The null check on the first line still runs before anything is read.
- The loop condition now also tests *p against the zero terminator.
- Because the terminator test is in the condition, the loop ends at index 6 of banana.
Where the Source Loop Diverges
The loop in the source tests only the pointer against nullptr. Its condition p != nullptr is true for every position of the array, including the terminator, because the pointer itself is not null there. So the source loop does not stop at the zero terminator at all. It keeps moving the pointer past the end marker, into memory that is not part of the array. The count it reports after that point is not a reliable count of the characters in the sequence, so the result can diverge from what you expected.
Common mistake Treating the null check as the stopping condition
What it looks likefor (; p != nullptr; ++p) on the array bananaWhy it failsThe null check asks whether the pointer points to any array. It says nothing about where the array's characters end, so the loop has no point at which it stops reading characters.
Do this insteadStop the loop on the zero terminator, for example with *p != 0, and keep the null check as a separate test before the loop.
Common mistake Assuming the terminator is not visited
What it looks likeExpecting the count to be 3 for banana because there are three a characters
Why it failsThe terminator is an element of the array and the pointer can reach it. A loop that does not test for it will continue past it.
Do this insteadCheck the element the pointer refers to, so that the loop ends when it reaches the zero value at the end of the array.
Common mistake Expecting a null pointer to run the loop body
What it looks likeCalling count_x with a null pointer and expecting the loop to count zero matches by reading the array
Why it failsA null pointer points to no array, and reading through it would be an error. The function must check for it before reading any element.
Do this insteadKeep the if (p == nullptr) return 0; check on the first line so that no element is read when there is no array.
Practice Tracing a Counting Loop
The zero-terminated array holds the characters a a b a followed by the terminator. Trace the corrected count_x loop with x = 'a'. Write down the pointer position, the element it reads, and the count after each pass. What is the final count, and at which position does the loop stop?
Hints
- Track the position of p as an index from 0, starting at index 0.
- The loop stops when *p is the zero terminator, which sits at index 4 in this array.
- Count only the passes where the element equals a.
Key Takeaways from the Walk
- 1A pointer walks a zero-terminated char array by moving forward with ++p, and each step advances one character.
- 2The zero terminator is stored as the last element of the array, and the loop's stopping condition must match it.
- 3The null check is a separate test that runs before any element is read, and it handles the case where there is no array.
- 4A loop that tests only the pointer against nullptr does not stop at the terminator, so its count can diverge from the expected result.
At a glance
- A pointer can walk a zero-terminated array of char one element at a time by moving forward with ++p.
- The zero terminator marks the end of the array, and the loop's stopping condition must match it.
- The null check handles a pointer that points to no array and is separate from the terminator check.
- A loop that tests only the pointer against nullptr does not stop at the terminator, which is where its result can diverge.