Why is a pointer pointing to null evaluating to true?

Viewed 99

I have a custom data structure that is essentially a circular linked list. The end node points to the start node which points to the first node in the list. The list works perfectly as intended, I'm just not quite sure why the following code behaves the way it does:

List::~List()
{
    Node* p = end->next;
    end->next = NULL;
    while (p != NULL)
    {
        Node* tmp = p;
        p = p->next;
        delete tmp;
    }
}

In the deconstructor, I set the current pointer (p) to end->next which, as I've said, points to start which points to the first data node. I then set end->next to null, which I assume means that start now points to nothing. But when it executes the next line while (p != NULL), it evaluates to true. Why is that?

2 Answers

I then set end->next to null, which I assume means that start now points to nothing.

It does not. Or at least, I assume so; we cannot know since you haven't shown start. I assume that start is a distinct object and thus modifying member of end will not modify it. start still points to where it pointed to before the assignment. What the assignment changes is that end->next no longer points to where it used to point (which according to you was the start node). Similarly p still points to where end->next used to pointe before it was set to null.

Consider an analogous example which may be easier to understand due to no pointers:

int end_next = 42;

int p = end_next;
end_next = 0;
assert(p != 0);

The modification of end_next has no effect on the value p. It works the same way with pointers:

Node a_node;
Node* end_next = &a_node;

Node* p = end_next;
end_next = nullptr;
assert(p != nullptr);

And still same way when the pointer is a member variable (here, the example should be quite close to the original program):

Node a_node;
Node* end->next = &a_node;

Node* p = end->next;
end->next = nullptr;
assert(p != nullptr);

You set end->next to null but p isn't null. p is whatever end->next was before you set it to null.

You'd need p to be a Node** if you want it to alias the value of end->next, Node **p = &end->next, then when you do *p != NULL you'd be following the p pointer back to the exact end->next, and if end->next had changed, *p would be able to see the updated value.

Here's a simple example:

#include <iostream>

struct Node {
    struct Node *next;
};

int main() {
    struct Node *end = new Node{new Node{nullptr}};
    struct Node *p = end->next; // pointer to a Node
    struct Node **pp = &end->next; // pointer to a pointer to a Node
    std::cout << end->next << std::endl; // addr(end->next)
    end->next = nullptr;
    std::cout << p << std::endl; // still addr(end->next)
    std::cout << *pp << std::endl; // 0
    delete p;
    delete end;
}

Here's a lines-and-boxes diagram:

After p = end->next:

.-------.
|  end  |  .------.
| [next]-->| Node |
`-------`  `------`
              ^
              |
           .------.
           |  p   |
           `------`

After end->next = NULL:

.-------------.
|     end     |
| [next=NULL] |
`-------------`

           .------.
           | Node |  
           `------`
              ^
              |
           .------.
           |  p   |
           `------`

You can see p is still not null just because end->next is.

With the double pointer **pp, after p = end->next, we have:

.-------.
|  end  |  .------.
| [next]-->| Node |
`---^---`  `------`
    |         ^
    |         |
.-------.  .------.
|  pp   |  |  p   |
`-------`  `------`

Now we have a pointer to the pointer end->next. After end->next = NULL, we have:

.-------------.
|     end     |  .------.
| [next=NULL] |  | Node |
`---^---------`  `------`
    |               ^
    |               |
.-------.        .------.
|  pp   |        |  p   |
`-------`        `------`

and *pp gives us end->next's current value, NULL.

Related