Programming in C++

The Free Store - Dynamic Memory Management

Gerald Senarclens de Grancy

Purpose of Dynamic Memory Management

Which problems does this solve?

  • Allows to dynamically allocate memory, and free it for reuse afterwards
  • Permits returning (pointers to) arrays from functions
  • Only way for non-C99 compilers to set array size at runtime
  • Can grow and shrink dynamic data structures (linked lists, trees, ...)
  • Required for using memory from the free store (heap)
  • Large amounts of memory can only be used from the free store

The size of the stack can be determined with ulimit -s

Which problems might this cause?

  • Requires working with pointers
  • Relatively slower compared to the stack
  • Programmer in charge of managing memory
  • Potential memory leaks (tools like valgrind help with debugging)

Some of these problems are (partly) solved by higher level languages

For example, C++ offers containers that manage memory for the programmer

Syntax

new
operator to reserve (allocate) memory on the free store
delete
operator to free (deallocate) memory from the free store
new[]
allocate all storage required for an array
delete[]
deallocate all storage reserved for an array

Type* var_name = new Type;  // allocate memory and store pointer in `var_name`
delete var_name;  // free memory
Type* array_name = new Type[num_elements];  // allocate memory
delete[] array_name;

Working with the Free Store (Heap)

Determine array size at runtime

const int size = 1234;  // any value determined at runtime
int array[size];

Unfortunately, the above only works for C99 capable compilers
ISO C++ does not support this (although some compilers do)

const int size = 1234;  // any value determined at runtime
int* array = new int[size];
// use the array ...
delete[] array;

No need to check if array == nullptr when the allocation fails.
C++ would throw an exception in that case.

Limited Size of the Stack

Determine the size of the stack via

$ ulimit -s  # or `ulimit -a` for all limits

Impossible to exceed the given stack size

#include <iostream>
int main(void) {
  int array[8192 * 1024 / 4];  // use 8192 kB if sizeof(int) = 4
  std::cout << "Survived?\n";
  return 0;
}

Try to compile and run exceed_stack_size.cpp

If large amount of memory is needed, one has to use the heap...

Examples

Allocate Memory for an Array

#include <iostream>

int main(void) {
  const size_t size = 12;  // any value determined at runtime
  int* array = new int[size];  // array on the free store
  int array_on_stack[3];  // array on stack (see visualization!)
  for (size_t i = 0; i < size; ++i) {
    array[i] = i * i;  // initialize the array ...
  }
  for (size_t i = 0; i < 3; ++i) {  // use the array ...
    std::cout << "array[" << i << "]: " << array[i] << std::endl;
  }
  delete[] array;  // otherwise memory will grow when the program keeps running
  return 0;
}

Visualize execution

Allocate Large Amount of Memory

#include <iostream>

int main(void) {
  size_t size = 8192 * 1024;  // 8 M * 4 B
  int* array = new int[size];  // 32 MB

  for (size_t i = 0; i < size / sizeof(int); ++i) {
    array[i] = i * i;  // initialize the array ...
  }
  std::cout << "Survived?\n";
  delete[] array;  // avoid memory leak
  return 0;
}

Download allocate_large_array.cpp

Return Array of Structs

#include <iomanip>
#include <iostream>

struct Point { double x; double y; };
Point* fetch_points() {
  Point* points = new Point[2];
  points[0] = (Point) { .x=4, .y=7 };
  points[1] = (Point) { .x=-5, .y=3 };
  return points;
}

int main(void) {
  Point* points = fetch_points();
  std::cout << std::fixed << std::setprecision(2);
  std::cout << "Point 1: " << std::setw(5) << points[0].x;
  std::cout << " / " << points[0].y << std::endl;
  std::cout << "Point 2: " << points[1].x << " / " << points[1].y << std::endl;
  delete[] points;
  return 0;
}

Visualize execution

Questions
and feedback...