Concepts / Pointer Basics and the Null Pointer

Pointer Basics and the Null Pointer

The belief goes like this: a pointer that has run off the end of its data is 'empty', and writing 0 or NULL is a way to say so.

  • Programming

The Shortcut Most Loops Start With

Many programmers arrive at pointer loops with a shortcut already in mind. The belief goes like this: a pointer that has run off the end of its data is 'empty', and writing 0 or NULL is a way to say so. The shortcut feels harmless, and in older code you will see it almost everywhere. This article starts from that belief, checks it against how pointers behave inside loops, and then builds a clearer way to write the same loops.

The belief to examine is this: 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. The reasons for preferring nullptr, however, are about clarity, and the confusion that the shortcut hides can cause real mistakes. By the end you should be able to explain why pointers can walk arrays, how a C-style string ends, and why a single, well-placed test is easier to trust than two scattered ones.

Advancing a Pointer One Element at a Time

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: it moves the pointer to the next element of that array. This is what makes a pointer useful as a loop variable. Each step of the loop advances the pointer by one element, and the code reads the element the pointer currently refers to.

What do you think happens?

A pointer p starts at the first character of the word cat. If p is incremented once, which character does it refer to?

  • the first character, c

  • the second character, a

  • the zero terminator

Reveal answer
Answer:

the second character, a

Incrementing a pointer that refers to an array element moves it to the next element. Starting at c, one ++ lands on a. Incrementing again would land on t, and one more would reach the terminator.

cfirst elementasecond elementpafter one ++tthird element\0zero terminatorrefers to
After one ++, the pointer refers to the second character. Each further ++ moves it one cell to the right, until it reaches the zero terminator.

Read the cells in order from left to right. A pointer that starts at the first cell and is incremented once refers to the second cell, which is the highlighted element in the diagram. Incrementing again moves it to the terminator. Notice what this replaces. An index variable would have to be advanced and compared against a length. A pointer carries its own position, so the loop does not need a separate counter just to find the next element.

The loop also does not need to know the number of characters in advance. It only needs to know when to stop. That stopping rule is the subject of the next section.

C-Style Strings and the Zero Terminator

Definition

A C-style string is a zero-terminated array of char. The characters of the text are stored in order, and a zero value placed after the last character marks where the data ends.

ccharachartchar\0zero terminatornextnextnext
The characters of cat occupy consecutive cells, and a zero terminator follows the last character. The loop stops when it reaches that terminator.

This is the key idea behind the loop. A function that counts characters in a C-style string does not receive a separate length. It walks the pointer forward until it reaches the zero terminator. The function's definition depends on the input being a valid C-style string, so the caller is responsible for providing one.

Counting Characters Without a Length

The following function counts the characters of a C-style string by advancing a pointer until it reaches the zero terminator. The pointer is set up before the loop begins, so the loop header has no initializer and the loop is naturally written as a while statement.

cpp
Output
count_chars("cat") returns 3
Notes on the code
  • const char* p = text; sets the pointer before the loop starts
  • *p reads the character the pointer currently refers to
  • ++p moves the pointer to the next element on each pass
  • The loop stops when the zero terminator is reached, so no length is passed in
Worked example

Tracing count_chars on the word cat

Trace the loop when text points to the C-style string cat.

  1. 1
    Start

    p refers to c, the first character. n is 0.

  2. 2
    First pass

    *p is c, which is not the zero terminator, so n becomes 1 and ++p moves p to a.

  3. 3
    Second pass

    *p is a, so n becomes 2 and p moves to t.

  4. 4
    Third pass

    *p is t, so n becomes 3 and p moves to the zero terminator.

  5. 5
    Stop

    *p is the zero terminator, the condition is false, and the loop ends with n equal to 3.

Result

The function returns 3, the number of characters before the terminator.

Output
Each pass of the loop increments n once. Three characters followed by the terminator give a count of 3.

Testing a Pointer Inside a Loop

A pointer used as a condition behaves the same way as comparing it with nullptr. The condition while (p) is equivalent to the comparison p != nullptr. Both forms ask whether the pointer refers to something, and both stop the loop when it does not. The first form is shorter, while the second form makes the comparison explicit, which many teams prefer for readability.

cpp
Notes on the code
  • The two loops ask the same question on each pass
  • A while statement repeats its body until its condition becomes false

A for statement bundles three jobs into its header: an initial setup, a condition, and a step performed after each pass. When the pointer is set up before the loop and only the test and the step remain, the header has empty parts, and a while statement states the same logic more directly. Both forms are valid. The choice depends on which one makes the loop's job easiest to read.

p set upbefore the looptest pwhile (p)bodyuse *p, then ++pexitloop endstruenext passfalse
With a single condition, the loop tests once on each pass. The exit happens at that one test, and the body moves the pointer before the test runs again.

The flowchart shows the shape that matters. The test sits in one place, the body runs only when the test succeeds, and the loop leaves through the same test. Nothing else needs to check the pointer.

Moving from Two Tests to 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 is unnecessary when the loop is built around the pointer's value. Once the pointer is moved along the array, the loop condition can carry the whole job of deciding when to stop.

Before
int count_chars(const char* text) {
    if (text == nullptr) {
        return 0;
    }
    int n = 0;
    const char* p = text;
    while (p != nullptr) {
        if (*p == '\0') {
            break;
        }
        ++n;
        ++p;
    }
    return n;
}

The loop checks nullptr inside its condition and again inside its body, with a break to leave on the terminator.

After
int count_chars(const char* text) {
    if (text == nullptr) {
        return 0;
    }
    int n = 0;
    for (const char* p = text; *p != '\0'; ++p) {
        ++n;
    }
    return n;
}

The missing-input check stays before the loop, and the loop condition alone decides when to stop, using the zero terminator.

C++ also offers a range-for statement, a simpler form designed for loops that traverse a sequence from beginning to end. It removes much of the bookkeeping that pointer loops require. It does not replace the pointer techniques in this article, which remain useful when you need explicit control over the position as you walk.

nullptr Compared with 0 and NULL

The belief examined at the start treats 0 and NULL as acceptable ways to write a null pointer. The recommendation in this course is to use nullptr instead. nullptr is the null pointer literal, so its purpose is written directly in the code. Writing 0 or NULL where a pointer is expected blurs the line between integers and pointers, and that blurring is the reason to prefer nullptr.

SpellingWhat it isWhy it matters in a pointer test
nullptrThe null pointer literalSays plainly that the value is a null pointer, so integers and pointers are not confused
0An integer valueCan be mistaken for an ordinary integer in pointer code, which hides the intent
NULLA name used in older codeAppears throughout older code, and the same confusion applies when it stands in for a pointer

Three ways to write a null pointer value, and what each one communicates to a reader

The reasons for preferring nullptr are about clarity. In a narrow sense a program with 0 or NULL may behave as expected, but the reader cannot tell at a glance whether the author meant an integer or a pointer. Choosing nullptr makes the intent visible on every test.

Mistakes Beginners Make with Pointer Loops

  • Common mistake

    Treating a pointer that has run off the end of its data as empty and writing 0 or NULL to say so

    What it looks like
    while (p != 0) { ... } inside a loop over a string
    Why it fails

    0 is an integer. Writing it where a pointer is expected hides the intent, and the reader cannot tell whether the test is about the pointer or about a number.

    Do this instead

    Write nullptr when the test concerns a pointer, so the comparison states its purpose.

  • Common mistake

    Assuming a non-null pointer means the loop should keep going until the end of the data

    What it looks like

    Testing only the pointer and forgetting to check for the zero terminator in a C-style string

    Why it fails

    The zero terminator, not the pointer's null state, tells the loop where the data ends.

    Do this instead

    Use the zero terminator as the stopping signal for a C-style string, and keep the null check for missing input.

  • Common mistake

    Repeating the same nullptr test before and inside the loop

    What it looks like

    A guard before the loop plus a second nullptr check inside the loop body

    Why it fails

    The second test is unnecessary once the loop condition carries the job of deciding when to stop.

    Do this instead

    Keep the guard before the loop and let the loop condition decide the exit.

  • Common mistake

    Expecting ++ on a pointer to add 1 to the address in bytes

    What it looks like

    Assuming p++ always moves to the next byte of a string

    Why it fails

    Applied to a pointer that refers to an array element, ++ moves the pointer to the next element of that array.

    Do this instead

    Think of ++p as moving to the next element, which is the cell the loop reads next.

When a pointer loop returns a surprising result, the failure 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. Checking which of these is wrong is faster than rereading the whole function. Ask what the pointer refers to at each test, and whether that is the value you expected.

Rewriting a Two-Test Loop

cpp
Notes on the code
  • The guard before the loop handles a missing input
  • The loop tests nullptr in its condition and again inside the body
MEDIUM

Rewrite the loop above so that it tests for the zero terminator in one place, the loop condition, and keep the guard before the loop for a missing input. Then explain in one sentence why the stopping test no longer needs a break.

Hints
  • The loop condition can compare *p with the zero terminator directly
  • A for statement with an empty initializer can start from a pointer set up before the loop
  • Keep the nullptr guard before the loop, because it handles a missing input

Key Takeaways

  • A pointer that refers to an array element moves to the next element when ++ is applied, so a loop can walk the array without an index variable.
  • A C-style string is a zero-terminated array of char, and the zero terminator tells the loop where the data ends.
  • nullptr is the null pointer literal and should replace 0 or NULL in pointer tests so that integers and pointers are not confused.
  • A pointer used as a condition is equivalent to comparing it with nullptr, so a loop can test for null in one place.

Related concepts

More concepts starting with P