Concepts / Using Arrays and Pointers in Loops

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.

  • Programming

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?

  • The pointer compared with nullptr

  • The character the pointer refers to, compared with the zero terminator

  • A separate length value passed in by the caller

Reveal answer
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.

pafter one ++a[0]10a[1]20a[2]30++p++prefers to
After one ++ on p, which array element does p refer to, and how does that map onto the indices a[0], a[1], a[2] when no index variable is used?
cpp
Output
10
20
30
Notes on the code
  • 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.

pstarts here'h'first character'i'second character'\0'zero terminator: loop stopsinitial position++p++p
Where does the zero terminator sit relative to the characters, and at which cell does a pointer-walking loop decide to stop?

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.

cpp
Output
2
0
Walk through it
  1. 1
    The pointer p starts at the first character of the string passed in.
  2. 2
    The condition checks two things: that p refers to something, and that the character under it is not the zero terminator.
  3. 3
    When both hold, the body adds 1 to n and moves p to the next character.
  4. 4
    When the terminator is reached, the condition is false and the loop ends with n holding the count.
Notes on the code
  • 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
Worked example

Tracing count_chars on "hi"

What value does count_chars return when it is given the C-style string "hi"?

  1. 1
    Start

    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'.

  2. 2
    Second pass

    p refers to 'i'. The condition is true. n becomes 2 and p moves to the terminator.

  3. 3
    Third test

    p refers to '\0'. The condition's second part is false, so the loop stops.

Result

The function returns 2.

p and *p checkp not null, character not'\0'n plus 1, ++ploop bodyreturn nloop exitstruenext passfalse
On each iteration, what is checked, what runs, and how does the pointer advance until the loop exits?

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.

SpellingWhat it isWhat a reader learns from it
nullptrThe null pointer literalThis value is a pointer with no target
0An integer valueCould be an integer or a pointer, depending on context
NULLA macro that expands to a null pointer constant in older codePointer intent is implied rather than stated in the language

Three ways to write a null pointer test

Best practice

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.

Before
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.

After
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 like
    while (p != nullptr) { ++n; ++p; } on "hi"
    Why it fails

    The 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 instead

    Stop 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 like
    while (p != 0) in a function that takes a pointer
    Why it fails

    The 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 instead

    Write 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 like

    A null check before the loop and another inside the body that never changes the result

    Why it fails

    The second check repeats a decision the loop condition already makes, so a reader has to verify both places.

    Do this instead

    Move the pointer test into the single loop condition.

  • Common mistake

    Dereferencing the pointer after it has moved past the terminator

    What it looks like

    Incrementing p once more after reading the '\0' and then reading *p

    Why it fails

    The characters past the terminator are not part of the string, so reading them is not a valid use of the data.

    Do this instead

    Check 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

EASY

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.
MEDIUM

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.

Related concepts

More concepts starting with U