Software Engineering Blog

Tipps und Tricks aus dem Leben eines Systemadministrators.

To free or not to free in C

von

In the programming language C the programmer is responsible for handling the (heap) memory allocations. This means, whenever memory is allocated on the heap using e.g. malloc, the programmer has to ensure that this allocation is released to the OS again. This can be done using free. So obviously the answer to the question in the title is that each malloc has to be followed by a free at some time. However there are some situations, where this answer is just too easy.

OS Memory Handling

For a more detailed answer we have to check how allocations are handled by the OS. As this would exceed this blog-post, we refer to the related Wikipedia article.

When is the memory returned to the OS?

Many people guess whenever a free is called, the released memory is almost instantly returned to the OS. However this does not hold in most situations. In most cases the memory is just released to the program, so that the next malloc (same program of course) first tries to use this memory (stored internally in a free-list). If the available memory is not large enough - which could also be due to fragmentation - fresh memory from the OS is requested. This is also stated in the GNU documentation of malloc

There is one exception for this rule of thumb: If the requested (and freed) memory is larger than a memory page. Then, the whole page is returned to the OS. As this is also not specified, some implementations might perform different. However most C-libs on at least linux-based OS do it this way.

Exceptions

A common problem related to allocations in C is the exception handling: As there is no RAII support (like in C++, see next section), the programmer has to track allocations in each part of the program. This is often tricky and leads to many lines of code for deallocation spread over many functions. To avoid double-frees - which generate a segmentation fault - it is best practice to null the pointer after freeing:

int* p = malloc(10 * sizeof(int));
//...
free(p);
p = null;

Freeing a null-pointer is specified and valid behavior.

Terminating the Application

This is almost the only situation, where memory must not be freed. In fact it is even recommended not to free the allocated memory directly before terminating the application, as the OS will do that for you. This speeds up the termination, as each to-be-freed memory location has to be transferred into the memory (e.g. from swap space) before actually freeing it. This can be avoided by not freeing the memory but relying on the "garbage collection" of the OS.

However under any circumstances, this strategy has to be chosen by design and not as a result of a leaky memory management.

What about valgrind

If following this strategy, memory profiling / tracking applications (e.g. valgrind) will always report memory-leaks. These should not be ignored, as they can hardly be distinguished from actual leaks. Hence, we recommend to use preprocessor macros to free the memory if using a tracker. This also ensures, that the application has no leaks except the missing frees at the end.

// ...
#ifdef VALGRIND
free(p);
#endif

Outlook: C++

If possible prefer C++ over C as this takes care of the memory handling in most situations. There, try to implement your application using STL containers instead of manually allocating memory regions. In short: Avoid new, delete, malloc, free.

The magic behind that is called RAII (Resource Acquisition Is Initialization). Whenever an object is constructed, the necessary resources are allocated and initialized. When the object later leaves the scope (even due to a exception), the resources are released again. This totally avoids the problems of tracking memory allocations (as described in section Exceptions).

And the best: If coding correctly, C++ code is as fast (or even faster - due to better knowledge of the types in the optimization step) as your C code.

However due to RAII it is tricky to tell C++ not to free the memory at the end. If really necessary, you could use your own allocator and set a flag (directly before terminating the program) not to free the memory on destruction of the allocator. But at least we know only very rare cases where this technique is used.

Best Practice Guide

A hint is to avoid small and short-lived allocations. If using C++ STL containers like std::vector this is not a big deal, as the allocator of the container takes care of efficient memory management. Otherwise use allocators (yes, that is also possible in C). Going into detail would exceed this topic horribly.

For tiny and short-lived allocations it is also possible to use alloca. Memory requested this way is allocated on the stack and automatically freed after leaving the function (not the scope).

And last but not least: Don't free memory at the end of the program, but do so by design and not because you lost the information. An easy way to check that is by following the approach described in "What about valgrind".

Zurück

Kommentare

Einen Kommentar schreiben

Bitte rechnen Sie 3 plus 9.

Ähnliche Beiträge

von

We reverse engineer a Dotnet Monitor in Windbg to see how it is internally implemented.

von

As SSDs are getting cheaper, low HDD / SSD ratios of 10/1 or better become an option. This article describes how to tune bcache for this scenario from an empirical perspective.