C is one of the most common languages that programs are written in. This is generally because of toolchain concerns, performance, familiarity and access to low-level hardware details, rather than how easy the language makes resource allocation.
Many resource leak bugs are caused by this, as the previous article describes, so it's important to be familiar with the various ways resources can be handled in C and C++.
Stack allocated memory
When you declare a variable, you declare its type, which is used to determine how much memory needs to be allocated for it.
static
variables (either globals, or variables explicitly marked as
static
in functions) are allocated at compile time and have memory
reserved for them.
auto
matic variables can only be declared in functions. Memory allocated
for auto
matic variables is released when the function returns, they
are called auto
matic because you don't need to do anything for the
memory to be released again.
This only works for memory though, and if you allocate too large an object on the stack, it is likely to crash your program, and release the memory back to the operating system, to be re-used in another process.
goto cleanup;
When automatic cleanup is not sufficient, you need to be explicit.
In C you have to call the appropriate function to release the resource. For memory allocated with malloc(3), it needs to be free(3)d, and files opened with open(2) need to be close(2)d.
Because you also need to check the return values of functions you call, you can end up with code like this:
char *foo(size_t bar, int baz){
int fd = -1;
char *mem = NULL;
fd = open("file/path", O_RDONLY);
if (fd != -1){
mem = malloc(bar);
if (mem != NULL){
int ret = qux(fd, mem, baz);
close(fd);
if (ret == 0){
return mem;
} else {
free(mem);
return NULL;
} else {
close(fd);
return NULL;
}
} else {
return NULL;
}
}
This doesn't scale well with the number of resources you require, and can easily end up with very wide code.
As an alternative, we're going to use goto
.
There's a lot of horror stories on the internet that say you should never
use goto
, but these generally come from the uninformed, jut parrotting
goto considered harmful without understanding the context; it was
written when you would traditionally use goto
, rather than a while
loop.
If it's good enough for the Linux kernel then it should be good enough for you.
The previous code can be instead written as:
char *foo(size_t bar, int baz){
int fd = -1;
char *mem = NULL;
fd = open("file/path", O_RDONLY);
if (fd == -1){
return NULL;
}
mem = malloc(bar);
if (mem == NULL){
goto cleanup;
}
if (qux(fd, mem, baz) == 0){
goto cleanup;
}
free(mem);
mem = NULL;
cleanup:
close(fd);
return mem;
}
catch { object.close(); }
Now we stray into the land of C++. Previously we said that you should use goto because there's no suitable language construct. C++ is a far more extended language, which does have a language construct to handle this.
char *foo(size_t bar, int baz){
int fd = -1;
fd = open("file/path", O_RDONLY);
if (fd == -1){
return 0;
}
try {
char *mem = new char[bar];
try {
qux(fd, mem, baz);
close(fd);
return mem;
} catch (...) {
delete[] mem;
throw;
}
} catch (...) {
close(fd);
throw;
}
}
In C++ you can handle exceptions rather than checking return codes and using goto. This reduces the amount of code at the call site dedicated to error handling, but unfortunately in C++, this still ends up with a lot of nested scopes.
Other languages, such as Java and Python have a finally
block, which
is always run, whether an exception was raised or not, which would mean
the above code wouldn't need to close(fd)
in the success path.
RAII
This doesn't bother C++ programmers, since for guaranteed resource cleanup, an idiom call RAII is preferred, where you have special objects, which release the resource in their destructors.
shared_ptr is a wrapper object that will destroy the object you pass it when every shared_ptr referring to the object has been unref'd.
auto_ptr will destroy the object when the auto_ptr goes out of scope.
The ifstream classes will close the file descriptor that they opened when they are destroyed.
std::vector<char> foo(int baz){
std::ifstream in;
in.open("file/path");
std::vector<char> mem;
qux(in, mem, baz);
return mem;
}
cleanup attribute
C has evolved in parallel to C++, and has its own ways of implementing something RAII-like.
GCC has an extension called the cleanup attribute.
This allows you to annotate a variable declaration with a function that will be called when it exits the scope.
This sounds a little ugly, but if you look at the cleanup attribute example, you can see that it uses a macro to hide the definition.
Others
This article has been mainly about cleaning up run-time resources, but you can also have specific code for cleanup of global resources.
You can use atexit(3) to clean up resources at process exit, provided it isn't terminated too aggressively.
In C++ there's also global object destructors, which let you specify the cleanup code per object.