Memory allocated dynamically with calloc
, malloc
, realloc
, or new
should be released when it is
not needed anymore. Failure to do so will result in a memory leak that could severely hinder application performance or abort it or the entire host
machine.
Why is this an issue?
Memory is a limited resource shared between all the applications running on the same host machine.
C and C++ do not automatically reclaim unused memory. The developer has to release the memory claimed for their application that is no longer
needed. Unlike the stack that automatically allocates local variables on a function call and deallocates them on a function return, the heap offers no
automatic memory management. The developer has to make sure to deallocate the memory they allocate dynamically on the heap.
This rule raises an issue when memory is allocated dynamically and not freed within the same function.
What is the potential impact?
Neglecting to free the memory leads to a memory leak.
The application that leaks memory will consume more and more of it over time, eventually claiming all the memory available on the host machine.
When this happens and the system runs out of memory, it typically does one of the following:
- The operating system (if any) terminates the application.
- The operating system (if any) terminates some other application, and the problem reoccurs when the reclaimed memory gets used up by the leaking
application.
- The operating system (if any) starts offloading some of the memory pages to disk and slows down some memory accesses by orders of magnitude.
- The entire system crashes as a whole and reboots automatically or hangs waiting for a manual reboot.
Moreover, memory leaks can help an attacker to take over the system. An attacker could use a memory leak to fill the memory with malicious code.
This facilitates remote code execution through another chained vulnerability.
Even if the attacker cannot take over the system she can intentionally trigger the condition leading to a memory leak to make use of the issue
above and cause denial-of-service (DoS) of the system.
A memory leak can have a significant impact on the energy footprint of an application.
- If an application demands more memory than necessary, the user will have to install more memory banks than necessary. Each memory bank consumes
additional power.
- As the application continues to reserve more and more memory, it places an increased load on the memory management subsystem. This increased
load can lead to a larger computation demand, which in turn translates to higher power consumption by the CPU.
Finally, memory leaks degrade the user experience. The user often experiences a system slowdown caused by the uncontrolled memory use of an
application. Delayed response time, system freezes, and crashes degrade the user experience and discourage the further use of the application.
Exceptions
If a function `return`s a pointer to the caller or stores it in an external structure, this pointer is said to escape (it is now
accessible outside of function, and no longer local to it). This includes storing the pointer in a static or global variable, passing it to a function
that can potentially do that, or returning the pointer directly or as part of an aggregate object.
The memory pointed to by an escaping pointer might be used somewhere else in the program. For that reason, the analyzer cannot proclaim a leak for
an escaping pointer by only looking at a function scope.
While in some cases the leak might be detectable in the scope of a caller, in others, the analyzer would need to simulate the entire program to
verify that the memory is not used anywhere, which is not feasible.
For this technical reason, this rule often ignores escaping pointers.
How to fix it
Ask yourself whether you need to allocate memory on the heap. If your object is small enough, in many cases allocating it as a local variable on
the stack is a better choice as it simplifies memory management.
If you do need to allocate it on the heap, the direct fix for a memory leak is to make sure you always deallocate memory.
In C++ you should use RAII (resource acquisition is initialization) idiom. See Going the extra mile.
Alternatively, you have to manually make sure that every exit of the scope of the pointer to the allocated memory is prepended by the deallocation
of that pointer.
Code examples
Noncompliant code example
bool fire(Point pos, Direction dir, State const& s) {
Bullet *bullet = new Bullet{pos, dir};
if (auto affected = bullet->hitAnyone(s)) {
affected->takeHit();
return true; // Noncompliant, the memory pointed to by bullet is not deleted
}
delete bullet;
return false;
}
Compliant solution
Use a local object
bool fire(Point pos, Direction dir, State const& s) {
Bullet bullet{pos, dir};
if (auto affected = bullet.hitAnyone(s)) {
affected->takeHit();
return true; // Compliant: bullet is destroyed and deallocated automatically
}
return false; // Compliant: bullet is destroyed and deallocated automatically
}
If you cannot use RAII or a local object, manually make sure memory is freed on every exit of the scope of the pointer.
Noncompliant code example
int fun() {
char* name = (char *) malloc (size);
if (!name) {
return 1;
}
if (condition()) {
return 2; // Noncompliant: memory pointed to by "name" has not been released
}
// ...
return 0; // Noncompliant: memory pointed to by "name" has not been released
}
Compliant solution
int fun() {
char* name = (char *) malloc (size);
if (!name) {
return 1; // Memory wasn't allocated, no need to free it
}
if (condition()) {
free(name);
return 2; // Compliant: memory is freed
}
// ...
free(name);
return 0; // Compliant: memory is freed
}
Pitfalls
Note that the execution can exit the scope in different ways:
-
return
from the function
-
break
from a switch
statement or a loop
-
goto
out of a code block (compound statement)
-
throw
a C++ exception
-
co_return
from an C++ coroutine
- End of the scope (
}
)
In the following example, even though the function frees memory before the explicit return
, the memory remains allocated when the
execution leaves the while
body via many other ways.
void fire(Point pos, Direction dir, State const& s) {
while (condition()) {
Bullet *bullet = new Bullet{pos, dir};
if (bullet->misfired()) break; // Noncompliant: memory is not freed
if (!condition()) {
delete bullet;
return;
}
// Noncompliant: memory is not freed
if (s.tooManyBullets()) throw Exception("Too many bullets");
if (bullet->timeIsUp(s)) goto end; // Noncompliant: memory is not freed
} // Noncompliant: at the end of iteration bullet leaks
end: // Memory allocated in the loop is not freed
std::cout <<"Bullet is lost\n";
}
This is why it is very difficult to avoid leaks when managing memory manually.
Going the extra mile
In C++, manually allocating and deallocating memory is considered a code smell.
It is recommended to follow the RAII idiom and create a class that manages the memory by allocating it when the object is constructed and
freeing it when it is destroyed. Furthermore, copy and move operations on such objects are designed such that this object can be passed by value
between functions (either as an argument or by being returned) in place of raw pointers.
Depending on the type, passing an RAII object operations may either:
- Allocate a new block of memory and copy the elements (
std::vector
).
- Transfer ownership of the memory to constructed object (
std::unique_ptr
).
- Use shared ownership and free memory when the last object is destroyed (
std::shared_ptr
).
The RAII object will take care of the deallocation of the memory when it is no longer used.
To correct the noncompliant example, use std::unique_ptr
:
bool fire(Point pos, Direction dir, State const& s) {
auto bullet = std::make_unique<Bullet>(pos, dir);
if (auto affected = bullet->hitAnything(s)) {
affected->takeHit();
return true; // Compliant: unique_ptr<Bullet> automatically frees memory
}
return false; // Compliant: unique_ptr<Bullet> automatically frees memory
}
Resources
Documentation
Standards
Related rules
- S5025 discourages manual memory management, which helps to avoid memory leaks.