Reputation: 3
I was learning about how to use classes and wrote the following code:
#include<iostream>
using namespace std;
class DQNode
{
public:
int data;
DQNode *next, *last;
DQNode(int num, DQNode *n, DQNode *l)
{
data = num;
next = n;
last = l;
}
};
class Deque
{
private:
DQNode *top, *bottom;
int size;
public:
Deque()
{
top = NULL;
bottom = NULL;
size = 0;
}
void addFirst(int inp)
{
DQNode nn(inp, NULL, top);
if(top != NULL)
{
(*top).next = &nn;
}
top = &nn;
if(bottom == NULL)
{
bottom = &nn;
}
}
void PrintAll()
{
cout<<top->data<<endl;
}
};
int main()
{
Deque n;
n.addFirst(5);
cout<<"yo"<<endl;
n.PrintAll();
}
The above code prints "yo" followed by a random integer, interesting part is that on removing cout<<"yo"<<endl;
the output is exactly as expected i.e 5.
So, if someone understands what is going wrong please help me.
Upvotes: 0
Views: 795
Reputation: 41760
You have undefined behavior.
This is what happens when you break the rules. When you're lucky, you program crashes with a segmentation fault. Other times, you're unlucky and your program misbehave.
How did you break the rules? Well, you access a dead object.
The source of the problem is happening in your addFirst
function. You store a DQNode
in your Deque
but that node dies.
You see, all local variable with automatic storage have well defined rules for when they live and die. Their lifetime is scope based. It look like this:
// Lifetimes
void addFirst(int inp) // inp
{ // |
DQNode nn(inp, NULL, top); // | nn
if(top != NULL) // | |
{ // | |
(*top).next = &nn; // | +--- <- top->next
} // | |
top = &nn; // | +--- <- top
if(bottom == NULL) // | |
{ // | |
bottom = &nn; // | +--- <- bottom
} // | |
} // | X -- nn dies
// X -- inp dies
Variable with automatic lifetimes are first to live, last to die. There is no exceptions. At the }
character at the end of the function, where its scope ends, all local variable are destroyed. nn
first, then inp
.
At that point, top
, bottom
or top->next
is still pointing to nn
, which has been destroyed!
Then, in your PrintAll
function is reading through the pointers=, which point to a destroyed variable. At that point, you read whatever is on the stack in that point of the program. A simple call to cout
can allocate variables in the places where nn
was, and assign whatever needed values. You pointer will still point there, and print garbage.
What can you do about it?
Well, you don't want automatic storage. It doesn't do what you want. You want to take control over the lifetime of the variables and deallocate them when you decide when you don't need them anymore. This is called the free store. You create objects there using dynamic allocation:
void addFirst(int inp)
{
// new will dynamically allocate the object on the free store.
DQNode* nn = new DQNode(inp, NULL, top);
if(top != NULL)
{
(*top).next = nn;
}
top = nn;
if(bottom == NULL)
{
bottom = nn;
}
} // the `nn` pointer dies, but not the object it refers to!
However, your program will never deallocate the variable on its own, you must do it manually or you'll get a memory leak.
~Deque() {
// traverse the tree
// delete all node in the tree
}
Luckily, there is also a tool called a std::unique_ptr
which will take care of deleting the allocated object when it dies.
The name unique_ptr comes from unique owner. The ownership of memory can be transferred, but there is always one and only one owner. When the owner dies, it deallocate the object from the free store:
// create a int equal to 1 on the free store using dynamic allocation
std::unique_ptr<int> int_a = std::make_unique<int>(1);
std::unique_ptr<int> int_b = nullptr;
// transfer ownership from int_a to int_b
int_b = std::move(int_a);
// int_a is null
// int_b point to a int equal to 1
// This is disallowed, you cannot have two owner, so no copy
// int_a = int_b;
// can have an observing raw pointer:
int* int_c = int_b.get();
// transfer ownership back
int_a = std::move(int_b);
// at that point int_a is equal to int_c
// int_a is the owner, int_c simply point to the same int.
// int_b is null
// Set the last owner to null:
int_a = nullptr;
// the int has been destroyed because there is no owner left.
// int_a is null, int_b is null
// int_c is a dangling pointer, point to the dead object.
Upvotes: 2