CS 70

Null Pointers

Default-Initialized Pointers

  • LHS Cow speaking

    As a reminder, here is the diagram we drew on the last page.

memory diagram

  • Horse speaking

    Hay, so one pointer in the array points to an Elephant.

  • LHS Cow speaking

    Yes!

  • Horse speaking

    But the other pointers are also Elephant*s, right?

  • LHS Cow speaking

    That's right…

  • Horse speaking

    So do they also point to Elephants?

  • LHS Cow speaking

    Ooh, good question!

When we created the array, all the pointers were default initialized.

Like primitive numeric types, default-initialized pointer variables have indeterminate values.

Every pointer variable contains an address, but it might not be an address that means anything!

In our example, we know that elephants[0] legitimately holds the address of an Elephant object (because we put one there), but we have no idea what address is stored in elephants[1].

If you had to guess…

What can we promise about what will happen if we had elephants[1]->getAge()?

The code would definitely

In C++, we are never supposed to follow a default-initialized pointer. If we break the rules and do so, the result is undefined behavior—we can't know what will happen as a result. What will probably happen is that your program will try to access the memory at that location, and then,

  • If that memory location is in a forbidden region (i.e., outside the memory space allocated to your program by the operating system), the program will crash with a segmentation fault (or similar error).
  • If the memory location is still inside the space allocated to your program, your code will assume that it will find an Elephant there, and execute the getAge() function using whatever data is there.
    • Depending on what's actually in that memory location and what happens when your code treats it like an Elephant, your program might or might not crash.
    • If your program doesn't crash, it will probably return some integer value based on whatever was in memory. Which could be the correct value, in which case your program will probably appear to be running properly, or an incorrect value, which could cause problems later when another part of your program gets an unexpected result.
  • RHS Cow speaking

    Note that, of these options, the last one is by far the worst!

  • Dog speaking

    What?!? Why would you want your program to crash??

  • LHS Cow speaking

    Because then I know something is wrong! If the program just continues along its merry way using garbage data, the issue will be much harder to track down.

Key Point: Watch out for default-initialized pointers! If your program is crashing or behaving strangely, one thing to check is whether any pointers that are involved have been properly initialized.

Null Pointers

  • Cat speaking

    Avoid calling functions on nonexistent Elephants. Got it. Good tip!

  • LHS Cow speaking

    Oh, it's worse than that. How can you tell that they don't exist?

  • Hedgehog speaking

    Oh, this doesn't sound good…

If you have an Elephant*, all you know is that it stores an address and it claims that there is an Elephant at that address.

Even default-initialized pointers store an address! And the stuff at that address is just bits, which could be an Elephant, for all you know.

There's no easy way for you to tell whether a pointer's value is the address of a real thing, or whether it is just pointing into space somewhere.

  • Alien speaking

    My home planet is frequently bombarded by improperly initialized pointers that point into space. We call them falling *s.

  • LHS Cow speaking

    I… don't think that's a thing. It's just a metaphor for a pointer that could be pointing anywhere in memory.

  • Cat speaking

    What we need is a way to set a pointer to point to nowhere rather than an arbitrary somewhere.

  • LHS Cow speaking

    Great idea! In fact, we can actually do that!

Pointer variables can store a special value, nullptr, which means “not a valid address”. We call a pointer with this value a null pointer.

The purpose of nullptr is to allow us to explicitly set a pointer variable to point to nowhere. Following a null pointer is also forbidden (because it doesn't point to anything), but it's much easier to avoid doing so by accident because we have a specific value to check against. Whenever we want to have a situation where sometimes there is a thing and sometimes there isn't, we can use a null pointer to represent the “there isn't” case, and check to see if a pointer is null before we try to use it.

  • Duck speaking

    But what actually happens if we accidentally follow a null pointer?

  • LHS Cow speaking

    Officially, it's undefined behavior, just like following a default-initialized pointer. But in practice, on our systems, it's almost always the case that the program will crash with a segmentation fault.

Array of Elephants and Not-Elephants

So let's change our initialization to fill the array with null pointers to start with.

Elephant* elephants[10];
for (size_t i = 0; i < 10; ++i) {
    elephants[i] = nullptr;
}
elephants[0] = new Elephant{"Peanut", 2};

Now we can tell which pointers really point to Elephant objects, and which ones don't!

Let's suppose we want to pass pointers from the elephants array to printAge()

Write the body of printAge so that it either prints the elephant's age or, if the pointer is null, prints No elephant here!.

void printAge(Elephant* e) {
}

Here's one possible implementation:

void printAge(const Elephant* e) {
    if (e != nullptr) {
        cout << e->getAge() << endl;
    } else {
        cout << "No elephant here!" << endl;
    }
}
  • Duck speaking

    This code catches null pointers, but it doesn't catch default-initialized pointers!

  • LHS Cow speaking

    That's right. It's still your responsibility to avoid that pitfall by properly initializing your pointers when you write your code. You actually can't protect against that issue once your code is running, because it could be any address, including valid ones with actual Elephants in them.

  • RHS Cow speaking

    But we can now intentionally set a pointer to point to nothing so we can tell that there's nothing there later on.

Deeper Dive: 0/null Initializing an Array

  • Rabbit speaking

    Here are some optional C++ details, if you are into learning a bit more about how things work. If not, feel free to skip this bit!

Technically, there is a better way to initialize a whole array with nullptrs.

It turns out that—for numeric types—if you use curly-brace initialization but don't give a value for everything, the rest of the array will be 0-initialized. For example,

int a[5]{1, 2};

would give us the array [1, 2, 0, 0, 0].

For pointer types, curly-brace initialization uses nullptr instead.

So

Elephant* elephants[10]{nullptr};

or even just

Elephant* elephants[10]{};

would give us an array of ten null pointers. The compiler can often perform this kind of whole-array initialization much faster than it can execute a for loop, so if you have a need-for-speed, this approach is better than allowing default initialization (and safer, too!).

Unfortuntately, you can't easily initialize arrays with any other value. So if you want an int array full of 42s, you are out of luck for shortcuts.

  • Rabbit speaking

    Fun fact, on a lot of systems, the nullptr value is actually “address zero”, so it is often thought of as a kind of zero for pointers. In fact, inspired by that fact, in a context where a pointer is expected, C++ lets you write nullptr as 0. But it's not actually the same as the integer 0, just a “cute” shorthand for nullptr!

  • Hedgehog speaking

    Gah, why do you have to make things so confusing?

  • Bjarne speaking

    It is part of our heritage.

  • LHS Cow speaking

    Don't use 0 for the null pointer, just use nullptr and don't worry about it!

(When logged in, completion status appears here.)