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:

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[]:

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: