C17 approval preserves the difficult zero-size realloc case
C17 has completed its final vote and entered publication processing. The history of zero-size realloc explains why a correction revision can clarify a contract without making every implementation behave identically.
A correction revision can settle the wording of an interface while preserving a choice that application code still has to avoid or understand. The zero-size case of realloc is an example in C17. ISO's lifecycle for ISO/IEC 9899:2018 records the close of voting on June 13 and entry into publication processing on the same day. The change is an approval milestone, not yet a claim that publication has completed.
The WG14 C11 clarification summary marks defect report 400 for C17. Its history concerns what a null result means when the requested size is zero. The problem is not simply whether an allocation succeeded. The caller also needs to know whether the function released the original allocation, because that determines which cleanup action remains valid.
C was my first language, and I still value its direct account of storage ownership. This defect is interesting because a familiar return value does not carry enough information to infer ownership in every case. The corrected wording makes that limitation explicit instead of turning the interface into a uniform operation across existing implementations.
A null result has more than one possible history
DR 400 begins with existing differences among AIX, BSD and glibc. The report distinguishes calls with a null pointer from calls with a non-null pointer. It also distinguishes a zero request from an ordinary nonzero allocation. These distinctions matter because the same returned pointer value can follow different effects on the original object.
In the report's BSD account, a null result from a zero-size request indicates allocation failure, and a non-null original pointer remains unchanged. In its glibc account, realloc(ptr, 0) returns null and frees the original allocation. The history includes a correction to the report's initial description of AIX's errno behavior, so the initial comparison should not be treated as an immutable statement of every platform detail.
The common point is the ownership ambiguity. An application may normally retain the original pointer until a resize succeeds. If a null result always meant that the original allocation remained live, the caller could continue using or eventually free it. If the zero-size operation has already deallocated it, that same recovery path becomes invalid.
Conversely, interpreting every null result as completed deallocation would also be wrong. The report's alternatives include behavior in which the original object remains allocated. A caller that discards its only pointer in that case loses the ability to release the object. Neither assumption follows merely from seeing a null return value with a zero requested size.
The correction separates zero and nonzero requests
The technical corrigendum recorded for DR 400 states that a failed nonzero request does not deallocate the old object. It handles a zero request separately. If the new object is not allocated for a zero request, whether the old object is deallocated is implementation-defined. If the old object remains allocated, its value is unchanged.
The return-value wording also changes. The report replaces wording about a new object that could not be allocated with wording about a new object that has not been allocated. That distinction avoids implying that every absence of a new object is the same kind of failure. The zero-size case can have a different permitted result even though the returned pointer is null.
The correction also marks invoking realloc with a zero size as obsolescent. That status is part of the documented direction, but it should not be expanded into a claim that every such invocation is forbidden. The same corrigendum still specifies implementation-defined behavior for the ownership question. Describing it as universally equivalent to free would erase precisely the distinction the report records.
This is a useful example of the difference between documenting variability and eliminating it. A standard can identify which decision an implementation must define without requiring all implementations to make the same decision. Application code then has a clearer contract, but that contract can still require knowledge of the chosen implementation.
A resize helper can make the empty case explicit
Code that controls its own allocation interface can separate release from nonzero resizing. The following example uses that separation to avoid passing a zero size to realloc:
#include <stddef.h>
#include <stdlib.h>
int resize_bytes(void **slot, size_t size)
{
if (size == 0) {
free(*slot);
*slot = NULL;
return 0;
}
void *next = realloc(*slot, size);
if (next == NULL)
return -1;
*slot = next;
return 0;
}
The caller must pass the address of an actual void * object. Its stored pointer must be null or valid for the allocation functions used here. Casting the address of some other pointer type to void ** is not part of this interface. The helper's purpose is limited to owning a byte allocation and replacing the stored pointer on a successful nonzero resize.
For a zero request, this helper defines its own policy by calling free and clearing the slot. For a nonzero request that returns null, it preserves the stored pointer. That latter behavior follows the corrected nonzero failure rule. The result code reports whether the requested operation completed under the helper's policy, so an empty allocation and a failed nonzero resize do not share the same result.
The helper does not promise to initialize new storage or compute a safe product of an element count and an element size. Those would be additional responsibilities for a different interface. Keeping the example in bytes avoids hiding an unchecked multiplication inside an explanation of the zero-size case. The ownership policy can be examined without also needing an arithmetic policy.
I prefer an explicit empty-case contract in code that owns allocation state. It gives the caller an answer about the original allocation without requiring it to infer that answer from a null pointer. That preference is an interface judgement, not a claim that C17 requires this particular helper or error convention.
Approval does not deploy a new allocator
The compiler and library also remain separate parts of the implementation. The GCC 8.1 standards documentation describes C17 as a version of C11 with corrections integrated. It says GCC applies the corrections in C11 mode too, with __STDC_VERSION__ distinguishing the selected revision. Selecting a new language mode therefore does not by itself replace the allocator supplied by a system library.
That matters when interpreting the approval milestone. ISO is advancing the specification through its publication process. A compiler can already expose a mode for that revision, and an existing library can already have one of the behaviors described by the defect history. Those activities have different timelines. None of them alone implies that every installed combination has suddenly acquired identical runtime behavior.
The clarification summary also distinguishes the status of different reports. Its table contains entries marked C17 alongside reports that remain open or under review. A correction revision should therefore be read through the included resolutions. The existence of a reported problem does not establish that this revision resolves it, and a proposal's suggested fix need not be the final recorded correction.
The history explains the wording that survives
DR 400 originates in the interaction between the Austin Group's POSIX work and WG14's C specification. The report describes an attempt to align the POSIX wording with the BSD behavior while recognizing that C's implementation-defined allowance appeared to admit other existing behavior. That history explains why an apparently simple request to make null unambiguous becomes a compatibility question.
The initial suggested change sought to associate a zero-size null result with an error indication. The recorded corrigendum instead spells out the separate ownership rules and marks zero-size use obsolescent. Reading only the original suggestion would miss that progression. The report's discussion and final wording are necessary to understand what the C17 status applies to.
The order of operations in the example also makes the retained state visible. The helper stores the result of a nonzero resize in a temporary variable before replacing the caller's pointer. A failed request therefore leaves the slot unchanged. Assigning the result directly into the slot would discard that distinction if the result were null, because the caller would then have lost its stored reference to the old object. The temporary variable preserves information that the nonzero failure contract says is still useful.
The approved revision consequently provides a more precise account of this boundary without promising one universal zero-size allocation policy. Nonzero resize failure preserves the old object. The zero-size case retains an implementation-defined deallocation choice. Application interfaces that need one predictable empty-state transition can express that transition directly and keep it separate from the allocator's zero-size behavior.