Memory Allocation

In this lesson, we will learn about an important subsection of memory management, i.e., memory allocation.

Introduction

Explicit memory management in C++ has a high complexity but also provides us with great functionality. Sadly, this special domain of C++ is not so well known.

For example, we can directly create objects in static memory, in a reserved area, or even in a memory pool. This functionality is often key in safety-critical applications in the embedded world.

  • C++ enables the dynamic allocation and deallocation of memory.

  • Dynamic memory, or the heap, has to be explicitly requested and released by the programmer.

  • We can use the operators new and new[] to allocate memory and the operators delete and delete[] to deallocate memory.

  • The compiler manages its memory automatically on the stack.

Smart pointers manage memory automatically.

Memory allocation

new

Thanks to the new operator, we can dynamically allocate memory for the instance of a type.

int* i = new int;
double* d = new double(10.0); 
Point* p = new Point(1.0, 2.0);
  • new causes memory allocation and object initialization.

  • The arguments in the brackets go directly to the constructor.

  • new returns a pointer to the corresponding object.

  • If the class of dynamically created objects is part of a type hierarchy, more constructors are invoked.

new[]

new[] allows us to allocate memory to a C array. The newly created objects need a default constructor.

   double* d = new double[5];
   Point* p = new Point[10];
  • The class of the allocated object must have a default constructor.

  • The default constructor will be invoked for each element of the C array.

The STL Containers and the C++ String automatically manage their memory.

Placement new

Placement new is often used to instantiate an object or a C array in a specific area of memory. In addition, we can overload placement new globally or for our own data types. This is a big benefit offered by C++.

char* memory = new char[sizeof(Account)]; // allocate std::size_t 
Account* acc = new(memory) Account; // instantiate acc in memory
  • The header, <new>, is necessary.

  • Can be overloaded on a class basis or globally.

Typical use-cases

  • Explicit memory allocation

  • Avoidance of exceptions

  • Debugging

Failed allocation

If the memory allocation operation fails, new and new[] will raise a std::bad_alloc exception. But that is not the behavior we want. Therefore, we can invoke placement new with the constant std::nothrow. This call will return a nullptr in the case of failure.

char* c = new(std::nothrow) char[10]; 
if (c){
  delete c; 
}
else{
// an error occured
}

New handler

In the case of a failed allocation, we can use std::set_new_handler with our own handler. std::set_new_handler returns the older handler and needs a callable unit. A callable unit is typically a function, a function object, or a lambda-function. The callable unit should take no arguments and return nothing. We can get the handler currently being used by invoking the function std::get_new_handler.

Our own handler allows us to implement special strategies for failed allocations:

  • request more memory
  • terminate the program with std::terminate
  • throw an exception of type std::bad_alloc

Further information


In the next lesson, we will study how to deallocate memory.

Get hands-on with 1200+ tech skills courses.