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

C API

#include <stdlib.h>
void *malloc(size_t size)
Allocates size bytes and returns a pointer to the allocated memory. The memory is not initialized.
void free(void *ptr)
Frees the memory space pointed to by ptr.
void *calloc(size_t nmemb, size_t size)
Allocates memory for an array of nmemb elements of size bytes each and returns a pointer to the allocated memory. The memory is set to zero.
void *realloc(void *ptr, size_t size)
Changes the size of the memory block pointed to by ptr to size bytes.
void *reallocarray(void *ptr, size_t nmemb, size_t size);
Changes the size of the memory block pointed to by ptr to be large enough for an array of nmemb elements, each of which is size bytes.

Working with the Heap (Free Store)

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

const int size = 1234;  // any value determined at runtime
int* array = malloc(size * sizeof(int));
// use the array ...
free(array);

Alternatively, one can be more explicit with a typecast

const int size = 1234;  // any value determined at runtime
int* array = (int*) malloc(size * sizeof(int));
// use the array ...
free(array);

Make Sure the Calls Were Successful

const size_t size = 1234;  // any value determined at runtime
int* array = (int*) malloc(size * sizeof(int));
if (array == NULL) {  // NULL pointer means no memory was allocated
  fprintf(stderr, "malloc %lu bytes failed\n", size);
  exit(EXIT_FAILURE);
}
// use the array ...
free(array);

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 <stdio.h>
int main(void) {
  int array[8192 * 1024 / 4];  // use 8192 kB if sizeof(int) = 4
  printf("Survived?\n");
  return 0;
}

Try to compile and run exceed_stack_size.c

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

Examples

Allocate Memory for an Array

#include <stdio.h>
#include <stdlib.h>
int main(void) {
  const size_t size = 12;  // any value determined at runtime
  int* array = (int*) malloc(size * sizeof(int));
  if (array == NULL) { exit(EXIT_FAILURE); }
  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 ...
    printf("array[%lu]: %d\n", i, array[i]);
  }
  free(array);  // otherwise memory will grow when the program keeps running
  return 0;
}

Visualize execution

Allocate Large Amount of Memory

#include <stdio.h>
#include <stdlib.h>
int main(void) {
  size_t size = 8192 * 1024;  // 8 MB
  int* array = (int*) malloc(size);
  if (array == NULL) {
    fprintf(stderr, "malloc %lu bytes failed\n", size);
    exit(EXIT_FAILURE);
  }
  for (size_t i = 0; i < size / sizeof(int); ++i) {
    array[i] = i * i;  // initialize the array ...
  }
  printf("Survived?\n");
  free(array);  // avoid memory leak
  return 0;
}

Download allocate_large_array.c

Return Array of Structs

#include <stdio.h>
#include <stdlib.h>
typedef struct point { double x; double y; } Point;
Point* fetch_points() {
  Point* points = (Point*) malloc(2 * sizeof(Point));
  points[0] = (Point) { .x=4, .y=7 };
  points[1] = (Point) { .x=-5, .y=3 };
  return points;
}

int main(void) {
  Point* points = fetch_points();
  if (points == NULL) {
    fprintf(stderr, "no memory allocated\n"); exit(EXIT_FAILURE);
  }
  printf("Point 1: %5.2lf / %5.2lf\n", points[0].x, points[0].y);
  printf("Point 2: %5.2lf / %5.2lf\n", points[1].x, points[1].y);
  free(points);
  return 0;
}

Visualize execution

Questions
and feedback...