Growing a circular buffer
A resize can begin with `double* ntab = realloc(c->tab, sizeof(double[nlen]));` followed by `if (!ntab) return nullptr;`. The call supplies the existing allocation and its requested new size. Keeping the result in `ntab` lets the code check for failure before replacing the stored pointer. section 13.1.1
`realloc`, declared in `<stdlib.h>`, grows or shrinks dynamically allocated storage. It returns a pointer to the resized object or null on failure. A null input pointer makes it allocate storage like `malloc`. Use a size strictly greater than zero. section 13.1 section 13.1.1
A successful call changes which pointer is valid:
- The returned address may equal the old address, or the runtime may provide a different object. Even when the addresses match, pointers derived from the original object become invalid. section 13.1.1
- Existing contents are preserved up to the applicable size: growth retains the old initial portion; shrinkage retains the initial portion that fits. section 13.1.1
- Do not separately free the old object after a successful relocation. `realloc` takes care of it while providing the new object. section 13.1.1 section 13.1.2
TAKEAWAY: Check the new pointer before replacing the old one. A failed attempt to enlarge a printing buffer takes a cleanup path that frees the original buffer; overwriting its only pointer with the null result would lose that path to cleanup. Eventually, free a retained allocation using a pointer returned by an allocation function, not an interior pointer or one already freed. section 15.6 section 13.1.2
Listing 15.2 Printing an array of numbers, optimized version
int fprintnumbers_opt(FILE*restrict stream,
char const form[restrict static 1],
char const sep[restrict static 1],
size_t len, size_t nums[restrict static len]) {
if (!stream) return -EFAULT;
if (len && !nums) return -EFAULT;
if (len > INT_MAX) return -EOVERFLOW;
int err = errno;
size_t const seplen = strlen(sep);
size_t tot = 0;
size_t mtot = len*(seplen+10);
char* buf = malloc(mtot);
if (!buf) return error_cleanup(ENOMEM, err);
for (size_t i = 0; i < len; ++i) {
tot += sprintf(&buf[tot], form, nums[i]);
++i;
if (i >= len) break;
if (tot > mtot-20) {
mtot *= 2;
char* nbuf = realloc(buf, mtot);
if (nbuf) {
buf = nbuf;
} else {
tot = error_cleanup(ENOMEM, err);
goto CLEANUP;
}
}
memcpy(&buf[tot], sep, seplen);
tot += seplen;
if (tot > INT_MAX) {
tot = error_cleanup(EOVERFLOW, err);
goto CLEANUP;
}
}
buf[tot] = 0;
/* print whole line in one go */
if (fputs(buf, stream) == EOF) tot = EOF;
CLEANUP:
free(buf);
return tot;
}
The optimized number-printing listing keeps `buf` while trying `realloc(buf, mtot)`, assigns the result only when it is non-null, and jumps to cleanup otherwise. section 15.6

