Confused about dangling pointers on a parent-child class

I am trying to understand dangling pointers using a Base Class and a Derived Class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include <iostream>

// Base class
class BaseClass
{
private:
protected:
    int size; // Size of the array

public:
    int *data; // Private array field
    BaseClass(int size) : size(size)
    {
        data = new int[size];
        std::cout << "BaseClass constructor" << std::endl;
    }

    // Destructor for BaseClass
    ~BaseClass()
    {
        delete[] data;
        std::cout << "BaseClass destructor" << std::endl;
    }

    // Function to print array elements
    void printData() const
    {
        for (int i = 0; i < size; ++i)
        {
            std::cout << data[i] << " ";
        }
        std::cout << std::endl;
    }
};

// Derived class
class DerivedClass : public BaseClass
{
public:
    // Constructor for DerivedClass
    DerivedClass(int size) : BaseClass(size)
    {
        for (int i = 0; i < size; ++i)
        {
            data[i] = i + 1;
        }
        std::cout << "DerivedClass constructor" << std::endl;
    }

    ~DerivedClass()
    {
        std::cout << "DerivedClass destructor" << std::endl;
    }
};

int main()
{
    // Create an object of the derived class

    BaseClass *base = nullptr;
    if (true)
    {
        DerivedClass derivedObject(5);
        base = &derivedObject;
    }

    if (base)
    {
        base->printData();
    }
    else
    {
        std::cout << "Derived is null" << std::endl;
    }

    return 0;
}


The output of the program is
BaseClass constructor
DerivedClass constructor
DerivedClass destructor
BaseClass destructor
1 2 3 4 5

What I do not understand is that the DerivedClass should have gone out of scope as noted by the destructor being called. I think that the base pointer should now be pointing to nothing.

I am just not sure why the output is not "Derived is Null"

I am using vscode on windows with MinGW as compiler.
> I think that the base pointer should now be pointing to nothing.
It's pointing to an object that no longer exists.

Using valgrind (memory analyser) on Linux

$ g++ -g -Wall baz.cpp
$ valgrind ./a.out 
==448715== Memcheck, a memory error detector
==448715== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==448715== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==448715== Command: ./a.out
==448715== 
BaseClass constructor
DerivedClass constructor
DerivedClass destructor
BaseClass destructor
==448715== Invalid read of size 4
==448715==    at 0x109481: BaseClass::printData() const (baz.cpp:30)
==448715==    by 0x1092C3: main (baz.cpp:69)
==448715==  Address 0x4de9c80 is 0 bytes inside a block of size 20 free'd
==448715==    at 0x484CA8F: operator delete[](void*) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==448715==    by 0x109424: BaseClass::~BaseClass() (baz.cpp:21)
==448715==    by 0x1095C2: DerivedClass::~DerivedClass() (baz.cpp:53)
==448715==    by 0x1092B0: main (baz.cpp:65)
==448715==  Block was alloc'd at
==448715==    at 0x484A2F3: operator new[](unsigned long) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==448715==    by 0x1093BE: BaseClass::BaseClass(int) (baz.cpp:14)
==448715==    by 0x1094F8: DerivedClass::DerivedClass(int) (baz.cpp:41)
==448715==    by 0x10929C: main (baz.cpp:63)
==448715== 
1 2 3 4 5 
==448715== 
==448715== HEAP SUMMARY:
==448715==     in use at exit: 0 bytes in 0 blocks
==448715==   total heap usage: 3 allocs, 3 frees, 73,748 bytes allocated
==448715== 
==448715== All heap blocks were freed -- no leaks are possible
==448715== 
==448715== For lists of detected and suppressed errors, rerun with: -s
==448715== ERROR SUMMARY: 5 errors from 1 contexts (suppressed: 0 from 0)

This (in reverse order) tells you
- where the block was allocated
- where it was freed
- where it attempted to use it after freeing it.

As a suggestion...
1
2
3
4
5
6
7
    // Destructor for BaseClass
    ~BaseClass()
    {
        delete[] data;
        data = nullptr;
        std::cout << "BaseClass destructor" << std::endl;
    }

So if there are dangling references to the base class, any attempt to use it will be met with a hard fault, rather than random success.

$ ./a.out 
BaseClass constructor
DerivedClass constructor
DerivedClass destructor
BaseClass destructor
Segmentation fault (core dumped)

As a suggestion...
// Destructor for BaseClass
~BaseClass()
{
delete[] data;
data = nullptr;
std::cout << "BaseClass destructor" << std::endl;
}

So if there are dangling references to the base class, any attempt to use it will be met with a hard fault, rather than random success.


Hello, thanks for this tip. It did stop the program after adding this.

Just want to confirm my understanding.

So my base pointer is already pointing to nothing, right?
I was just confused about the output and spent a day trying to figure this out.

Because my lecture notes and from what I have read from the internet told me that it should be a dangling pointer but the output of my program is different.

Another thing I am confused:
1
2
3
4
5
6
7
8
    if (base)
    {
        base->printData();
    }
    else
    {
        std::cout << "Derived is null" << std::endl;
    }


Why my checking of the base pointer resolves to a true? I was expecting this to resolve to a false since the object it is pointing to was already destroyed.

I am confused with C++ .
Last edited on
Your if/else test it before the base and derived get destroyed. They both get destroyed at the end of the program, AFTER your if/else test.

Destruction is in the opposite direction of creation, so Derived gets destroyed first and then the base.


Quite hard to understand this during my program flow execution. I could not wait for my program to end before checking if it is now pointing to a an object that was destroyed.

However, I have just realized that I should not be checking for dangling pointers using this code if(base){}

It should be like the code below to check if the object that it is pointing to was already destroyed. if (base == nullptr)

The complete code that works from my expectation goes like this below
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#include <iostream>

// Base class
class BaseClass
{
private:
protected:
    int size; // Size of the array

public:
    int *data; // Private array field
    BaseClass(int size) : size(size)
    {
        data = new int[size];
        std::cout << "BaseClass constructor" << std::endl;
    }

    // Destructor for BaseClass
    ~BaseClass()
    {
        delete[] data;
        std::cout << "BaseClass destructor" << std::endl;
    }

    // Function to print array elements
    void printData() const
    {
        for (int i = 0; i < size; ++i)
        {
            std::cout << data[i] << " ";
        }
        std::cout << std::endl;
    }
};

// Derived class
class DerivedClass : public BaseClass
{
public:
    // Constructor for DerivedClass
    DerivedClass(int size) : BaseClass(size)
    {
        for (int i = 0; i < size; ++i)
        {
            data[i] = i + 1;
        }
        std::cout << "DerivedClass constructor" << std::endl;
    }

    ~DerivedClass()
    {
        std::cout << "DerivedClass destructor" << std::endl;
    }
};

int main()
{
    // Create an object of the derived class

    BaseClass *base = nullptr;
    if (true)
    {
        DerivedClass derivedObject(5);
        base = &derivedObject;
    }

    if (base != nullptr)
    {
        base->printData();
    }
    else
    {
        std::cout << "Derived is null" << std::endl;
    }

    return 0;
}


The output of this program is:

BaseClass constructor
DerivedClass constructor
DerivedClass destructor
BaseClass destructor
Derived is null


Which is what I am expecting.

But I am still open to comments from C++ experts about this situation though.


Sorry, I stand corrected. The output is not what I am expecting still. Any advice from experts?
I could not quite get this as what I am reading is different from the output of my compiler.
Last edited on
I think your code here is always true:

1)
1
2
3
4
5
    if (true)
    {
        DerivedClass derivedObject(5);
        base = &derivedObject;
    }


Write it like this without if():
1
2
        DerivedClass derivedObject(5);
        base = &derivedObject;



2) Then in your code here your input on the parameter and the protected member variable ARE THE SAME NAME:
 
BaseClass(int size) : size(size)


Change to something like:
 
BaseClass(int inSize) : size(inSize)


Then you will get the printout and your base and pointer get destroyed AFTER your printout, which is what is intended.

Base constructor
Derived constructor
printout....
Derived destructor
Base destructor


3) Your code:
1
2
3
4
5
6
7
8
    if (base != nullptr)
    {
        base->printData();
    }
    else
    {
        std::cout << "Derived is null" << std::endl;
    }


This code will ALWAYS be true, since the base and derived don't get destroyed until the program ends, and AFTER your if/else statement above and you don't set the data pointer to null in your destructor.
Last edited on
base is the dangling pointer (it points to an object that no longer exists).

This is going to bake your noodle.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int main()
{
    // Create an object of the derived class

    BaseClass *base = nullptr;
    if (true)
    {
        DerivedClass derivedObject(5);
        base = &derivedObject;
    }

    DerivedClass derivedObject(10);

    if (base != nullptr)
    {
        base->printData();
    }
    else
    {
        std::cout << "Derived is null" << std::endl;
    }

    return 0;
}


$ g++ -g -Wall baz.cpp
$ ./a.out 
BaseClass constructor
DerivedClass constructor
DerivedClass destructor
BaseClass destructor
BaseClass constructor
DerivedClass constructor
1 2 3 4 5 6 7 8 9 10 
DerivedClass destructor
BaseClass destructor


Not only is base dangling, it now just happens to point to a freshly created new instance of DerivedClass(10) that just happened to re-use the same location in memory that DerivedClass(5) did.

base is the dangling pointer (it points to an object that no longer exists).


Yes this is my understanding also.

The reason I have this code below is to simulate that the base pointer is being pointed to an object that was created and destroyed immediately

1
2
3
4
5
6
    BaseClass *base = nullptr;
    if (true)
    {
        DerivedClass derivedObject(5);
        base = &derivedObject;
    }


Now, the pointer is dangling after the above code executes.

So the code below, I was expecting that base is now dangling since the object(Derived) it was pointing to was destroyed.

The purpose of the logic below is to check if the base pointer is now dangling.
1
2
3
4
5
6
7
8
    if (base != nullptr)
    {
        base->printData();
    }
    else
    {
        std::cout << "Derived is null" << std::endl;
    }


Is this not possible to do in C++? I cannot wait for my program to terminate before checking if the base pointer is pointing to an object that was destroyed.
Last edited on
Base* dangles only when the program reaches the end and when derived gets destroyed (which happens before base gets destroyed).

As long as he forcefully does not delete the derived class AND THEN uses the base* then it is OK, as it is it shouldn't be a problem.

There is only one "new" for the "data" member in base and it gets destroyed in the base destructor. The 2 objects do not get created with "new" and they are just regular pointers to objects.

At the end both, I think both constructors get destroyed with the modified code and the invalid base* pointer address also gets destroyed.

It is only a problem if one deletes derived and then uses base* after.
I just read your last post. So the if(true) is intentional then and designed to purposely teach you a lesson on dangling pointers.

You can also write it like this as well, but the if(true/false) serves as a quick and nice switch:
1
2
3
4
    {
        DerivedClass derivedObject(5);
        base = &derivedObject;
    }


The life of the derived object "derivedObject(5)" is within the new code body between the two braces and the same is true for your "if(true{}" as well. Once you reach the end of your braces the derived object gets destroyed along with the inherited base class. That is why you then see derived destroyed, followed by base destroyed before your printout.

Your base pointer was created before the new code block and still exists.

Although, now your base pointer still refers to the memory location that it no longer should and the outcome can be unpredictable, depending on what happens to those memory locations. Yes, it dangles and the solution is to realize this and set it to null right after the pointed to location goes out of scope and not to use it as if it does afterwards. You can still check it for null though.

1
2
3
4
5
6
if (true)
    {
        DerivedClass derivedObject(5);
        base = &derivedObject;
    }
base = nullptr;


Last edited on
I just read your last post. So the if(true) is intentional then and designed to purposely teach you a lesson on dangling pointers.

You can also write it like this as well, but the if(true/false) serves as a quick and nice switch:
{
DerivedClass derivedObject(5);
base = &derivedObject;
}


The life of the derived object "derivedObject(5)" is within the new code body between the two braces and the same is true for your "if(true{}" as well. Once you reach the end of your braces the derived object gets destroyed along with the inherited base class. That is why you then see derived destroyed, followed by base destroyed before your printout.

Your base pointer was created before the new code block and still exists.

Although, now your base pointer still refers to the memory location that it no longer should and the outcome can be unpredictable, depending on what happens to those memory locations. Yes, it dangles and the solution is to realize this and set it to null right after the pointed to location goes out of scope and not to use it as if it does afterwards. You can still check it for null though.

if (true)
{
DerivedClass derivedObject(5);
base = &derivedObject;
}
base = nullptr;


Yes, I intentionally put the if(true) there for me to understand the lifecycle of the object.

I was dumbfounded with what is printed on my console and I have to re-read all of my notes.

Judging from your answer and based on my shallow understanding.
In C++, there is no mechanism to determine if a pointer is pointing dangling or pointing to a destroyed object.

The best way to manage it on our program is to manually take note of these circumstances and set the pointer to nullptr when not in use.

base = nullptr;

As what you have shown here. In this manner, we could check if it is dangling or not. Is my understanding correct?

I have read somewhere about smart pointer topics but I have not used them yet as I want to stick to plain old pointers and understood how it works.
Last edited on
Yes, text book answer is to set to null when what its pointing to goes out of scope or no longer needed. However, sometimes as a newb it is hard to identify this, such as what you have experienced with your particular example.

Smart pointers is what I was going to suggest too, as it does the garbage collecting for you, but as students we have to learn all ways.

Also remember that if you set a pointer, without new/delete that it gets deleted automatically at the end of a program. Problem in your case is that it was pointing to an object that became deleted before the base pointer was deleted. It is a good example and teaching tool.

When using new/delete for dynamically allocated memory, it is always one delete/delete[] for every new.

I am new too and tons more to learn.

shaefayejem wrote:
In C++, there is no mechanism to determine if a pointer is pointing dangling or pointing to a destroyed object.

Exactly. Dereferencing a dangling pointer leads to undefined behaviour so it's not safe to assume anything. You should simply avoid doing it.
Exactly. Dereferencing a dangling pointer leads to undefined behaviour so it's not safe to assume anything. You should simply avoid doing it.


Yes, thanks for confirming. I learned about it just now by doing this simple exercise.
Yet another way to see:
1
2
3
4
5
6
{
  int a;  //#1
  int* b = new int;  //#2
  delete b;  //#3
   //#4
}  //#5 

#1 creates "object", local int variable with name "a"

#2 creates two "objects", one local pointer with name "b"
and one unnamed int in dynamically allocated memory (lets call it "c")
The "b" has address of "c"

#3 does not modify "b". It still has an address. The "c" is destroyed, so the memory at address is no longer allocated, it is "free"

#4 we still have "a" and "b", but "b" is dangling

#5 at end of scope the local variables "a" and "b" are destroyed
Yeah - delete doesn't change the value of the used variable. It just marks the pointed to memory as being available for use. It's been mooted before that delete should set the var to nullptr but that is unlikely to happen. C's free() has the same issue.
A reference can also dangle for the same reason, the object referred to is destroyed. Usually by going out of scope.

A pointer can be assigned a null value, a reference can never be.

There are some other differences between pointers and references:

https://favtutor.com/blogs/cpp-reference-vs-pointer

C doesn't have references, only pointers.

Using raw pointers is fraught with peril that even experienced coders can be caught by. Smart pointers were created to overcome the problems.
Topic archived. No new replies allowed.