Skip to content

C Dynamic Memory and Ownership Conventions

malloc gives a program suitably aligned storage; it does not create an owning object abstraction. C APIs communicate ownership through names, documentation, types, and disciplined control flow.

Allocation is a fallible size calculation

c
#include <stdint.h>
#include <stdlib.h>

if (count > SIZE_MAX / sizeof *values) {
    /* requested byte count is not representable */
    return 0;
}
int *values = malloc(count * sizeof *values);
if (values == NULL && count != 0) {
    /* handle allocation failure */
}

The division check proves the multiplication is representable before it is evaluated. C23 adds checked-arithmetic facilities to the broader toolbox, but portable baselines still require an explicit policy.

calloc allocates space and initializes all bytes to zero. All-bits-zero is suitable for integer zero and null characters, but the most general portable model should not equate it with every typed zero representation without considering the target.

Every allocation needs one ownership story

For each pointer, be able to answer:

  • Who owns the allocation now?
  • Which functions merely borrow it?
  • Can ownership transfer?
  • Which function deallocates it?
  • Are aliases invalidated on resize or free?
c
struct buffer {
    unsigned char *data;
    size_t length;
    size_t capacity;
};

void buffer_destroy(struct buffer *buffer) {
    free(buffer->data);
    *buffer = (struct buffer){0};
}

Resetting fields helps prevent accidental reuse through that object, though it cannot repair aliases held elsewhere.

realloc can move storage

Do not overwrite the only pointer before checking failure:

c
void *new_data = realloc(buffer->data, new_capacity);
if (new_data == NULL) {
    /* original allocation still belongs to buffer */
    return 0;
}
buffer->data = new_data;
buffer->capacity = new_capacity;

On success, the old pointer is invalid even if the numeric address happens to remain. Interior pointers and aliases into the allocation must be considered invalidated.

Zero-size allocation behavior has standard-version and implementation subtleties. Avoid using realloc(pointer, 0) as a portable synonym for free; express deallocation directly.

Cleanup paths make ownership visible

C has no automatic destructor at block exit. A single cleanup section can centralize reverse-order release:

c
int load_report(const char *path) {
    FILE *file = fopen(path, "rb");
    if (file == NULL) return 0;

    char *data = malloc(4096);
    if (data == NULL) {
        fclose(file);
        return 0;
    }

    int success = read_report(file, data);
    free(data);
    fclose(file);
    return success;
}

For many resources and failure points, a carefully scoped goto cleanup avoids duplicated release logic. This is one of the places where a blanket ban on goto imported from other languages harms clarity.

Allocators must match deallocators

Memory returned by malloc, calloc, or realloc is released with free. Platform and library APIs may require their own release function. Crossing module or runtime boundaries with “allocate here, free anywhere” can be invalid, particularly on Windows with different C runtimes.

An API that returns ownership should provide or clearly name the matching destructor:

c
struct report *report_create(void);
void report_destroy(struct report *report);

Alternative ownership strategies can simplify systems

Not every object needs an independent heap allocation. Arrays of values, caller-provided buffers, stack storage, arenas, pools, and region lifetimes can improve locality and make cleanup simpler. Choose based on lifetime and workload rather than treating malloc as a constructor.

Optional prompts

Debug: Why is pointer = realloc(pointer, size) dangerous?

Answer: On failure, realloc returns null while leaving the original allocation alive. Overwriting the only pointer leaks it. Store the result temporarily and commit on success.

Explain: Does setting one freed pointer to null make other aliases safe?

Answer: No. It only changes that variable. All aliases to the ended allocation remain dangling.

Further reference