CLA (C language) study materials
CLA (C language) study resources

Realloc Growth and a Pointer-Sized memset

CLA (C language) · Put your knowledge to work in context.

All case studies

Realloc Growth and a Pointer-Sized memset

A telemetry collector reads an unknown number of 4-byte int samples. It begins with int *data = malloc(4 * sizeof(int));, capacity 4 and count 0. For each sample it does:

if (n == cap) {
    data = realloc(data, cap * 2 * sizeof(int));
    cap *= 2;
}
data[n++] = v;

After a 10-million-sample run the process is killed under a 64 MB memory cap; the payload itself is about 40 MB. Under an injected allocation failure the program segfaults at data[n++] = v. A reviewer also flags memset(data, 0, sizeof(data));, intended to clear the whole allocated array. The first malloc return is never checked. The platform has 4-byte int and 8-byte pointers. The collector must fail cleanly rather than crash or be killed.

1. Under an injected allocation failure the program segfaults at `data[n++] = v`. Explain the `realloc` defect and give a corrected growth step with overflow protection.

Read the worked answer

The assignment data = realloc(data, ...) overwrites the only pointer to the old block. If realloc fails it returns NULL and leaves the old block allocated, so the program loses the old pointer (a leak) and then dereferences NULL at data[n++], causing the segfault. Use a temporary:

size_t newcap = cap * 2;
if (newcap < cap || newcap > SIZE_MAX / sizeof *data) {
    free(data);
    return -1;
}
int *tmp = realloc(data, newcap * sizeof *data);
if (tmp == NULL) {
    free(data);
    return -1;
}
data = tmp;
cap = newcap;

newcap < cap catches wrap of the doubling; the division check catches newcap * sizeof *data overflow. Also check the initial malloc(4 * sizeof(int)) for NULL before the first store, and decide whether to free data on failure or return it to the caller. The key rule is never to lose the original pointer until realloc has succeeded.

2. The payload for 10 million `int` samples is about 40 MB, yet the process exceeds a 64 MB cap. Explain how the growth policy causes this and suggest a concrete change.

Read the worked answer

Doubling reaches a capacity of 16,777,216 elements: 8,388,608 × 2. At 4 bytes per int, that is 67,108,864 bytes (64 MiB) just for the new block, which exceeds a 64 MB cap and leaves no room for the old block or allocator overhead. During realloc, the old block (33.5 MB) and the new block may coexist before the copy completes, so the peak can approach 100 MB. The payload is only 40 MB; the slack and transient copy are the problem. Use a smaller growth factor, such as 1.5×: capacity would reach about 13.1 million elements, roughly 52 MB, reducing the overshoot and the transient peak. Alternatively, reserve exactly the needed size if the count is known, process in fixed-size chunks, or memory-map/spool to a file. Also impose an explicit maximum (if (newcap > LIMIT / sizeof *data) fail) so the collector fails cleanly instead of being killed.

3. What does `memset(data, 0, sizeof(data))` actually clear, and what should replace it? Include the unchecked first allocation.

Read the worked answer

data is an int *, so sizeof(data) is the size of a pointer, 8 bytes on the stated platform, not the size of the allocated array. The call clears only the first 8 bytes, i.e. two int elements, leaving the rest indeterminate. The correct size is cap * sizeof *data (or n * sizeof *data for initialized elements): memset(data, 0, cap * sizeof *data);. Do this only after allocation succeeds. The initial malloc(4 * sizeof(int)) must be checked: if it returns NULL, the first data[n++] = v dereferences a null pointer. Handle it like the growth failure path. Note that zeroing an int array with memset is portable only because all-bits-zero is a valid representation of 0 for the target; calloc expresses the intent more directly for the initial block.