Advanced Memory Management: Dynamic Allocation, Part 1

By Andrei Milea

malloc and free, new and delete

Dynamic allocation is one of the three ways of using memory provided by the C/C++ standard. To accomplish this in C the malloc function is used and the new keyword is used for C++. Both of them perform an allocation of a contiguous block of memory, malloc taking the size as parameter:
int *data = new int;    
int *data = (int*) malloc(sizeof(int));         //notice the use of sizeof for portability




This memory block can be used whenever needed during the program execution or until explicitly deallocating it, unlike the automatic memory which is available only inside the function or block of instructions where it was declared. Allowing a program to allocate dynamic storage every time it needs more until the program stops can cause it eventually to run out of available space. To prevent this behavior C++ provides the delete operator with the job of recycling a segment of memory allocated with new:
delete (data);
The C function for memory deallocation is free() and has the same behavior as delete, it frees the space pointed by data for future use:
free(data);
If the allocated memory is not freed when it's no longer necessary it will result in a memory leak. It is not specified what will happen to the leaked memory, but contemporary operating systems collect it when the program terminates. Memory leaks can be very dangerous because the system may run out of memory. To eliminate them, C++ provides a destructor member for every class where the programmer should deallocate all the memory it allocates inside the class. In other languages like Java or C# a garbage collector is used that figures out which memory blocks are no longer needed and deletes them, taking the burden of deallocation from the programmer's shoulders, but adding some overhead in runtime. In C++, you can use smart pointers that hold on to a piece of memory and deallocate that memory in their destructors.

Even though malloc and free are available in C++, their use is not recommended; it is always preferred to use new and delete, especially when working with objects. Also, notice the cast to int in the malloc allocation. This is not required in C because the C standard allows implicit cast between void *, which is the type returned by malloc, and other pointer types. In fact, in C, casting malloc is considered undesirable. But if we want to use malloc in C++ code we must explicitly cast the pointer returned by malloc to the appropriate type. Actually the use of void* pointers in C++ is not recommended because it can break multiple inheritance, that is, in a multiple inheritance hierarchy some of the classes can view different values of the this pointer (the original value with some offset) and casting to void* can break the protection mechanisms of C++. If a void* cast is needed in this situation it is recommended to use a dynamic_cast or static_cast as soon as possible, to adjust the this pointer automatically.

The backward portability with malloc and free is useful in C++ for supporting legacy C code and to allow implementing (overloading) the new and delete operators using calls to malloc/free. Because the operator new does more than just allocating memory (it also calls the object's constructor), it is not allowed to use free for data allocated with new or vice versa (delete with malloc).

The advantages of using new and delete over their older relatives malloc/free are the following:
  • new and delete point to the correct memory type (they are type safe), so casts are not necessary.
  • new invokes the constructor, allowing the initialization of objects, and delete invokes the destructor.
  • new figures out the size it needs to allocate, so the programmer doesn't have to specify it.
  • new throws an exception of type std::bad_alloc when it fails, instead of only returning a NULL pointer like malloc, so the programmer doesn't have to write the deprecated check
    if(NULL==data) error();
    
  • You can specialize the behavior of new by overloading it for your specific class (this will be discussed further in a following article) or even replacing the global one (this can generate problems, however, because someone might rely on the default behavior).

In some cases throwing an exception is not desirable (probably for working with legacy codebases that do not expect or handle exceptions, and also perhaps to avoid the overhead of supporting exceptions). For this situation the standard provides an exception-free version of new and new[]:
void* operator new(std::size_t size, const std::nothrow_t&) throw();
void* operator new[](std::size_t size, const std::nothrow_t&) throw();
Dynamic memory can be used not only to store data for your application; you can use it for functions too. A pointer that points to a function (function pointer) can be declared like this:
int (*f)(int,int);      //this is a pointer to a function taking 2 ints as arguments and returning one 
f = &pow;               //now function pow can be called using the pointer f
Even if in C++ you have other means to avoid the using of function pointers, the new operator can be used to allocate memory for a pointer to a function:
int (**f)(int,int) = new (int (*) (int,int));
The above line can be quite confusing for people not used with function pointers. In order to get a better view on them, see the function pointers tutorial.

Dynamic arrays

Creating a dynamic object is different than creating an array of objects and C++ handles the two situations differently. The new and delete operators are used for creation of dynamic instances of classes or built-in types, while new[] and delete[] create and destroy dynamic arrays.
Myclass *my_class;
my_class = new Myclass [size];          //size must be of type int 
//do work with my_class
delete [] my_class;
The call to new [] in the above example allocates memory for the entire array and then calls the default constructor for every object in the array in an increasing order. The returned value is a pointer to the beginning of the allocated storage, the first element in the array. In the end, the delete [] operator calls the destructor for each object in reversed order and then deallocates the memory.

There are two limitations in creating a dynamic array of objects. One of them is that you can't create a multidimensional array explicitly, like for automatic (stack-based) arrays:
Myclass  **my_class;
my_class = new Myclass[size1][size2];   //this will yield a compilation error
It is a consequence of the way the memory is allocated, in fact the storage for a multidimensional array is not contiguous; each element contains a pointer to another array.

In order to obtain a 2-dimensional array you can do something similar with this:
my_class = new Myclass* [size1];                //note that the type is a Myclass pointer
for(int i=0; i<size1; i++)
        my_class[i] = new Myclass [size2];
To deleted the allocated memory you must go through the first array and apply delete[] to every element and then delete[] the main one:
for(int i=0; i<size; i++)
        delete [] my_class[i];
delete[] my_class;
The above methods can easily be generalized to obtain and destroy an n-dimensional array.

The other important restriction is that the explicit initialization is banned, so you can't pass parameters when building an array of objects with new; only the default constructor gets called.

Related articles

Dynamic Memory Allocation, Part 2: Dynamic Memory Allocation and Virtual Memory

Dynamic Memory Allocation, Part 3: Customized Allocators with Operator New and Operator Delete

Dynamic Memory Allocation, Part 4: Common Memory Management Problems in C++

Understanding Pointers

Using auto_ptr to avoid memory leaks.

Finding memory leaks with Valgrind