In C programming, you allocate memory to create an object by asking the runtime for a number of bytes:
x = malloc(16);
if (x == NULL) {
/* allocation failed - hit memory limit? */
}
However, often what you actually want is an array of objects of a certain size, so you do:
x = malloc(n * sizeof(*x));
if (x == NULL) {
/* allocation failed - hit memory limit? */
}
However, if the value of n
is not known, the multiplication may
overflow, which can cause a smaller number of bytes to be returned than
requested, which would be bad, so you do:
x = calloc(n, sizeof(*x));
if (x == NULL) {
/* allocation failed - hit memory limit or overflow? */
}
However, calloc also pre-fills the array with zeroes, which may be an
unnecessary performance penalty, and also can’t resize reallocations
efficiently in case you want to realloc
rather than malloc
, so
if you’re on a system that has
reallocarray(3)
(an extension available on BSD and GNU systems), you can:
newptr = reallocarray(oldptr, n, sizeof(*oldptr));
if (newptr == NULL) {
/* error occurred, clean up oldptr... */
}
reallocarray
does not solve one ambiguity. The C standard does not
specify the behaviour for zero-sized allocations. Some implementations
return NULL
to indicate an error, and others return a pointer to
inaccessible memory. Does NULL
always indicate an error?
For this reason, NetBSD has
reallocarr(3), which can replace
malloc
, realloc
, and reallocarray
. Since it only updates the
result if allocation succeeded, using a temporary variable for
oldptr
is unnecessary:
if (reallocarr(&ptr, n, sizeof(*ptr)) != 0) {
/* allocation failed, clean up ptr... */
}
This allows us to improve and simplify allocation in our large and very long-lived C codebase: