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.
0 of 10 checks passed
Your progress is saved on this device.