Objects and arrays can be allocated dynamically with new and delete, or malloc and
free. This can be useful when the amount of memory required is not known at compile time. Four typical uses of dynamic memory allocation can be mentioned here:
• A large array can be allocated dynamically when the size of the array is not known at compile time.
• A variable number of objects can be allocated dynamically when the total number of objects is not known at compile time.
• Text strings and similar objects of variable size can be allocated dynamically.
• Arrays that are too large for the stack can be allocated dynamically. The advantages of dynamic memory allocation are:
• Gives a more clear program structure in some cases.
• Does not allocate more space than needed. This makes data caching more efficient than when a fixed-size array is made very big in order to cover the worst case situation of the maximum possible memory requirement.
• Useful when no reasonable upper limit to the required amount of memory space can be given in advance.
• The process of dynamic allocation and deallocation of memory takes much more time than other kinds of storage. See page 25.
• The heap space becomes fragmented when objects of different sizes are allocated and deallocated in random order. This makes data caching inefficient.
• An allocated array may need to be resized in the event that it becomes full. This may require that a new bigger memory block is allocated and the entire contents copied to the new block. Any pointers to data in the old block then become invalid.
• The heap manager will start garbage collection when the heap space has become too fragmented. This garbage collection may start at unpredictable times and cause delays in the program flow at inconvenient times when a user is waiting for
response.
• It is the responsibility of the programmer to make sure that everything that has been allocated is also deallocated. Failure to do so will cause the heap to be filled up. This is a common programming error known as memory leaks.
• It is the responsibility of the programmer to make sure that no object is accessed after it has been deallocated. Failure to do so is also a common programming error. • The allocated memory may not be optimally aligned. See page 129 for how to align
dynamically allocated memory.
• It is difficult for the compiler to optimize code that uses pointers because it cannot rule out aliasing (see page 80).
• A matrix or multidimensional array is less efficient when the row length is not known at compile time because of the extra work needed for calculating row addresses at each access. The compiler may not be able to optimize this with induction variables. It is important to weigh the advantages over the disadvantages when deciding whether to use dynamic memory allocation. There is no reason to use dynamic memory allocation when the size of an array or the number of objects is known at compile time or a reasonable upper limit can be defined.
The cost of dynamic memory allocation is negligible when the number of allocations is limited. Dynamic memory allocation can therefore be advantageous when a program has one or a few arrays of variable size. The alternative solution of making the arrays very big to cover the worst case situation is a waste of cache space. A situation where a program has several large arrays and where the size of each array is a multiple of the critical stride (see above, page 89) is likely to cause contentions in the data cache.
If the number of elements in an array grows during program execution, then it is preferable to allocate the final array size right from the beginning rather than allocating more space step by step. In most systems, you cannot increase the size of a memory block that has already been allocated. If the final size cannot be predicted or if the prediction turns out to be too small, then it is necessary to allocate a new bigger memory block and copy the contents of the old memory block into the beginning of the new bigger memory block. This is inefficient, of course, and causes the heap space to become fragmented. An alternative is to keep multiple memory blocks, either in the form of a linked list or with an index of memory blocks. A method with multiple memory blocks makes the access to individual array elements more complicated and time consuming.
A collection of a variable number of objects is often implemented as a linked list. Each element in a linked list has its own memory block and a pointer to the next block. A linked list is less efficient than a linear array for the following reasons:
• Each object is allocated separately. The allocation, deallocation and garbage collection takes a considerable amount of time.
• The objects are not stored contiguously in the memory. This makes data caching less efficient.
• Extra memory space is used for the link pointers and for information stored by the heap manager for each allocated block.
• Walking through a linked list takes more time than looping through a linear array. No link pointer can be loaded until the previous link pointer has been loaded. This makes a critical dependency chain which prevents out-of-order execution.
It is often more efficient to allocate one big block of memory for all the objects (memory pooling) than to allocate a small block for each object.
A little-known alternative to using new and delete is to allocate variable-size arrays with
alloca. This is a function that allocates memory on the stack rather than on the heap. The space is automatically deallocated when returning from the function in which alloca was called. There is no need to deallocate the space explicitly when alloca is used. The advantages of alloca over new and delete or malloc and free are:
• There is very little overhead to the allocation process because the microprocessor has hardware support for the stack.
• The memory space never becomes fragmented thanks to the first-in-last-out nature of the stack.
• Deallocation has no cost because it goes automatically when the function returns. There is no need for garbage collection.
• The allocated memory is contiguous with other objects on the stack, which makes data caching very efficient.
The following example shows how to make a variable-size array with alloca: // Example 9.3
#include <malloc.h>
void SomeFunction (int n) { if (n > 0) {
// Make dynamic array of n floats:
float * DynamicArray = (float *)alloca(n * sizeof(float)); // (Some compilers use the name _alloca)
for (int i = 0; i < n; i++) {
DynamicArray[i] = WhateverFunction(i); // ...
} } }
Obviously, a function should never return any pointer or reference to anything it has allocated with alloca, because it is deallocated when the function returns. alloca may
not be compatible with structured exception handling. See the manual for your compiler for restrictions on using alloca.
The C99 extension supports variable-size arrays. This feature is controversial and is only available in C, not in C++. You may use alloca instead of variable-size arrays since it provides the same functionality.