Release the Vector Properly to Avoid Memory Leaks in C++
std::vector is a popular data structure for C++ programming and it’s essential to release the memory within the vector when it is no longer used to avoid the memory leaks.
In this blog post, I will present an analysis on how to achieve the release in a proper way based on several typical situations.
Memory Leaks
The program owns stack memory and heap memory during the execution. The stack memory is closely related to the invocations: every invocation corresponds to a stack frame which is pushed into the stack memory. The stack frame stores the local variables of the current invocation and is destroyed after the invocation returns. However, the heap memory requires more manual efforts. The programmer can apply for a block of heap memory by new
and release it by delete
. An allocated heap memory needs the release operation to avoid the memory leaks, which have the potential risks to run out of the computer resources.
Effect of delete
Operator
As discussed before, delete
can free heap memory. More specifically, it frees the memory that the pointer points to directly if the pointer belongs to a primitive type such as int
, char
. It will invoke the destructor function first then free the memory pointed by pointer if the pointer’s type is some class.
class Test{
private:
int* ptr;
public:
Test() {
cout << "Enter constructor\n";
ptr = new int(0);
};
~Test() {
cout << "Enter destructor\n";
delete ptr;
};
};
int main(void) {
Test *t = new Test();
delete t; // delete will call destructor then free the memory that stores the object
}
After the execution of the above code example, the output is:
Enter constructor
Enter destructor
Note that delete
invokes the destructor function and frees the heap memory that the pointer points to, but it doesn’t free the pointer itself. In the above example, the pointer t
is a local variable and stored in stack. The programmer does not need to free it manually.
Apply delete
on Pointer of Vector
Since the vector is a class, delete
will invoke the destructor function of vector then free the memory that the vector pointer points to. The destructor function of vector will try to invoke the destructor function of each elements unless they do not have.
Thus, when the inner elements are of primitive types such as int
, char
, and pointer
which have no destructor function, delete
operation will only free the memory that vector pointer points to (not include the pointer of vector which takes 32 or 64 bits). When the elements are the instances of the class, delete
operation will invoke the destructor functions of each inner class instances and then free the vector itself (also, not include the pointer of vector).
One must take attention when the inner elements are the pointer of the class instances. The pointer belongs to the primitive type no matter it points to another primitive type variable or class instances. This is because even if the pointer type differs, the pointer itself is always a 32 or 64 bits variable that stores an address. Thus, when the programmer conducts delete
on a vector contains the pointer of the class instances, the destructor of instances will not be invoked. In this case, the inner elements are of primitive type instead of class instances. The programmer must free the instance manually before using delete
.
Some Cases
In this subsection I will present some wrong/correct cases about releasing vector. I leverage ASAN for memory leak detection.
case 1. vector stores primitive elements
int main(void) {
vector<int> *vec = new vector<int>();
vec->push_back(1);
delete vec;
return 0;
}
In this case, variable vec
can be safely freed by delete
.
case 2. vector stores the pointer of primitive elements
int main(void) {
vector<int *> *vec = new vector<int *>();
int *int_pointer = new int(-1);
vec->push_back(int_pointer);
delete vec;
return 0;
}
Memory leak happens in this case. The code delete vec
can not free the heap memory applied by new int(-1)
. To avoid the leak, the code should be like the follows:
int main(void) {
vector<int *> *vec = new vector<int *>();
int *int_pointer = new int(-1);
vec->push_back(int_pointer);
delete int_pointer;
delete vec;
return 0;
}
case 3. vector stores primitive elements, but they are in heap
int main(void) {
vector<int> *vec = new vector<int>();
int *int_pointer = new int(1);
vec->push_back(*int_pointer);
delete vec;
return 0;
}
Memory leak happens in this case. The code delete vec
can not free the heap memory applied by new int(1)
. To avoid the leak, the code should be like the follows:
int main(void) {
vector<int> *vec = new vector<int>();
int *int_pointer = new int(1);
vec->push_back(*int_pointer);
delete int_pointer;
delete vec;
return 0;
}
case 4. vector stores class instances
int main(void) {
vector<Test> *vec = new vector<Test>();
Test t;
vec->push_back(t);
delete vec;
return 0;
}
This case will cause double free. The culprit is the destructor of class Test is called two times while the Test::ptr points to the same address during these two invocation. After vec->push_back(t)
executes, there are two instances of class Test. The one in vector while another one is the local variable t
. The one in vector stems from t
and their member ptr
points to the same address. The statement delete vec
invokes the destructor of the instance in vector. And after the variable t
is out of scope, destructor of t
is invoked too. The details are shown as the following figure.
case 5. vector stores the class instance which is in heap
int main(void) {
vector<Test> *vec = new vector<Test>();
Test *t = new Test();
vec->push_back(*t);
delete vec;
return 0;
}
Memory leak will happen in this case. Although destructor of t
is called and Test::ptr is freed by delete vec
, the heap memory that stores instance t
is not freed. However, the programmer may not use delete t
directly since this will invoke the destructor of t
again. A graceless way is to add operator delete(t)
before return 0
to free the allocated heap memory.
case 6. vector stores the pointers of class instances
int main(void) {
vector<Test *> *vec = new vector<Test *>();
Test *t = new Test();
vec->push_back(t);
delete vec;
delete t;
return 0;
}
No memory leak happens in this case. Note that the programmer needs to free the instance manually.
The article ends up. There are more possible situations besides the aforementioned cases. However, smart pointers like unique_ptr
, shared_ptr
, and weak_ptr
are more recommended for better programming practice in modern C++. You may refer to them to make your code neat and graceful and achieve better memory management.