Design Article
The yin and yang of dynamic allocation
Dan Saks
5/4/2008 1:00 PM EDT
A few months back, I explained that each object in C and C++ has one of the following three storage durations: static, automatic, and dynamic:1
• An object with static storage duration has storage that is allocated at program startup and remains allocated at the same location until the program terminates.
• An object with automatic storage duration has storage that's allocated upon entry into a block (typically a function body) and deallocated upon exit from that block.
• An object with dynamic storage duration has storage that's allocated when the program calls an allocation function, such as malloc in C or an operator new in C++. The object's storage lasts until the program passes the address of that object to a corresponding deallocation function, such as free in C or an operator delete in C++.
While it's nearly impossible to write a useful program without using both static and automatic allocation, programs can--and do--manage to get by without any dynamic allocation. This month, I'll look at the case for and against using dynamic allocation.
Quick takes
Static storage allocation typically has no run-time cost. The initialization of statically allocated objects may take place at run time, but the allocation itself usually takes no time. (Of course, the allocation still takes up memory space.) Unfortunately, in applications that juggle diverse collections of objects, big and small, using static storage squanders memory and imposes rather arbitrary restrictions on program behavior.
For example, suppose your application uses two different-sized regions of memory, say, buffers and blocks. Your target system might have enough memory to statically allocate 100 buffers or 250 blocks. Should you carve up space for 50 buffers and 125 blocks, or is a typical application more likely to need up to 60 buffers but only 100 blocks? And what about those occasions when program needs 80 buffers but only 50 blocks? Tough luck?
The run-time cost of automatic storage allocation is usually pretty low--often only an instruction or two on each function entry and exit. On microcontrollers with limited stack space, the cost can be higher. Automatic storage uses memory very efficiently, but it's useless for objects with lifetimes that persist beyond function calls.
By comparison, dynamic storage allocation is much slower than either static or automatic allocation. A call to malloc (in C or C++) or operator new (in C++) may execute tens and occasionally hundreds of instructions in a quest for storage of the requested size. Deallocating storage by calling free (in C or C++) or operator delete (in C++) often requires comparable effort.
You do get something for that effort: dynamic storage allocation uses memory much more flexibly than static storage allocation. Using dynamic storage may add a little overhead to the storage for each object, so instead of 100 statically allocated buffers, you might have room for only 99 or 98 dynamically allocated buffers. But now any run of the program can have up to 98 buffers and no blocks, or up to 123 blocks and no buffers, or any balance of buffers and blocks within those limits that doesn't exceed total available memory.
Risky business
Dynamic memory allocation brings with it a number of risks. Some developers, especially for embedded systems, find these risks too great to accept and follow the advice of the MISRA-C guidelines. (MISRA is the Motor Industry Software Reliability Association in the UK). According to their guidelines:2
Rule 20.4 (required): Dynamic heap memory allocation shall not be used.This precludes the use of the functions calloc, malloc, realloc, and free.
There is a whole range of unspecified, undefined and implementation-defined behaviour associated with dynamic memory allocation, as well as a number of other potential pitfalls. Dynamic heap memory allocation may lead to memory leaks, data inconsistency, memory exhaustion, non-deterministic behaviour.
Although I have some quibbles with the wording, it covers most of the major concerns. These concerns also apply to operator new and operator delete in C++. Let's look at them (and my quibbles) in some detail.
Undefined behavior is what a program exhibits when it does something erroneous that the compiler and run-time system need not detect (often because they just can't). For example, freeing a pointer to storage that's already been freed, or that never was allocated, produces undefined behavior. The undefined behavior typically corrupts the heap--the data structures that support the dynamic memory. The corruption in the heap usually spills over into other data and run-time structures, and the program eventually crashes.




bgat
5/8/2008 10:01 AM EDT
Memory pools don't just reduce fragmentation, they _eliminate_ it. Period. That's what they're designed for.
Many of the toolchains I work with are provided with a K&R-type malloc(). I usually augment that with a memory pool implementation that uses the malloc API, but pools for the actual allocations. Thus, I get the benefits of both worlds.
To suggest that the "implementation of malloc is unknown most of the time" is... rubbish. If your toolchain provides one, it is by definition not unknown. You might not have the source code, but you have the assembly language if you are so inclined. Or you can just replace the "unknown [because I don't want to track it down]" implementation with your own.
Finally, consider the possibility that MISRA was "designed by sheeple, for sheeple". I fundamentally disagree with their objections to dynamic memory allocation, at least in situations where I'm inclined and granted the authority to do so responsibly. I'm glad to see Dan take up a contrary position as well.
Entire classes of systems are markedly improved by the careful but liberal use of dynamic memory allocation. Some of the systems I've worked on would not have been possible had we followed MISRA on this point.
Sign in to Reply
rbv
5/8/2008 1:55 PM EDT
Perhaps you never heard of internal fragmentation? Pools eliminate external fragmentation, not internal fragmentation. This is still a big win because internal fragmentation doesn't increase over time the same way external fragmentation does.
Sign in to Reply
bgat
5/8/2008 4:25 PM EDT
"Perhaps you never heard of internal fragmentation?"
Indeed, I haven't. But I'm not convinced we're talking about the same thing. Or using the same definition for "fragmentation", even.
I've re-read your posting several times now, and it makes less sense to me each time.
Sign in to Reply
DrOctavius
5/21/2008 11:57 PM EDT
I have been working with MISRA-2004 for 4 years and we have never used a malloc (some of our projects have more than 100KLOC!).
I think that malloc and free have more disadvantages than advantages in embedded systems.
Sign in to Reply
rajesh284
12/18/2011 12:31 PM EST
Dynamic memory allocation is discussed here
http://clinuxpro.com/static-and-dynamic-memory-allocation
Sign in to Reply