Skip to content

Chapter 12 of 21

Memory Management

Allocate, resize, own, and release dynamic memory correctly.

34 minutes 10 quick checksBy Subha Prasad
Lesson 12 of 21Course navigation

Lesson content

Read, practise, then check your understanding

Dynamic allocation creates objects whose size or lifetime is decided at runtime. <stdlib.h> provides malloc, calloc, realloc, and free. Successful allocations are suitably aligned for ordinary object types; failure returns NULL.

Allocate an array safely

int *values = malloc(count * sizeof *values);
if (values == NULL && count != 0) {
    return ALLOCATION_FAILED;
}

/* use values[0] through values[count - 1] */
free(values);
values = NULL;

Using sizeof *values keeps the allocation correct if the pointer type changes. Before multiplication, ensure count <= SIZE_MAX / sizeof *values to prevent size overflow.

malloc leaves bytes indeterminate. calloc(count, size) allocates an array and zeroes all bytes; for integer types this represents zero, but byte-zero is not promised to be every possible semantic null representation on every historical target.

Resize without losing ownership

realloc may resize in place or move the allocation. On failure it returns NULL and leaves the original object valid.

int *temporary = realloc(values, new_count * sizeof *values);
if (temporary == NULL && new_count != 0) {
    /* values is still owned and valid */
    handle_failure();
} else {
    values = temporary;
}

Never overwrite the only owning pointer before checking. A size of zero has subtle historical and standard-version behavior; handle empty collections explicitly.

Ownership and lifetime

For every allocated object, define:

  • who owns it now;
  • whether another pointer merely borrows it;
  • which function releases it;
  • what happens on partial failure.

Each allocation needs exactly one eventual free. Double-free, use-after-free, reading uninitialized memory, and out-of-bounds access are undefined behavior.

Cleanup on failure

Acquire resources in order and release them in reverse. A single cleanup label prevents duplicated, inconsistent cleanup:

int load(void) {
    char *buffer = malloc(4096);
    FILE *file = NULL;
    int status = -1;
    if (!buffer) goto cleanup;
    file = fopen("data.bin", "rb");
    if (!file) goto cleanup;
    status = read_data(file, buffer);
cleanup:
    if (file) fclose(file);
    free(buffer);
    return status;
}

Run tests with AddressSanitizer, UndefinedBehaviorSanitizer, or Valgrind. Tools complement—not replace—clear ownership design.

Knowledge check

Answer every question correctly to complete this chapter.

Which allocation function initializes the allocated bytes to zero?
What should code do immediately after malloc may fail?
How are bytes returned by malloc initialized?
Why use sizeof *pointer in an allocation expression?
What happens to the original allocation when realloc returns NULL for a nonzero size?
What is the safe way to assign realloc’s result?
What is a memory leak?
What is a double-free?
Why check count <= SIZE_MAX / sizeof *p before multiplication?
Which testing tool can expose use-after-free?

0 of 10 checks passed

Your progress is saved on this device.