November 16, 2025·4 min read·engineeringcdeferstandardscompatibility

The C cleanup draft adopts the _Defer keyword

Revision 4 requires _Defer and recommends defer as an alternative spelling. Its cleanup rules still depend on lexical scope and the ordering of return evaluation.

The fourth committee draft of C's proposed cleanup specification changes the required keyword to _Defer. It recommends that implementations also provide defer, but treats that shorter spelling as recommended practice. The distinction affects what source code can rely on from an implementation conforming to this draft. It is more specific than saying the cleanup proposal has merely received another revision.

The WG14 document log dates N3734 to November 16. Its revision 4 text describes extensions to ISO/IEC 9899:2024 and names JeanHeyd Meneide as project editor. The proposal remains a separate draft technical specification. Its appearance does not add _Defer to the requirements of every C23 compiler.

The spelling change is visible against September's N3688, whose required keyword was defer. N3734 retains the terminology of a defer statement, while its grammar uses _Defer. Existing explanations that use the shorter spelling need to distinguish the required form from the recommended alternative when discussing this revision.

Source compatibility includes keyword spelling

C was my first language, and its long life makes source compatibility part of nearly every language change. A new keyword can affect existing identifiers independently of the feature's runtime behavior. I read the new distinction as a practical compatibility choice, though the draft's wording itself establishes the spelling requirement rather than explaining every motivation behind it.

For an implementation following N3734, the unambiguous draft form is _Defer followed by the deferred statement. A simple illustration acquires storage, checks success, and associates the release operation with the following scope. This is proposed syntax, not portable C23 source.

#include <stdlib.h>

int allocate_work_area(void)
{
    void *area = malloc(128);
    if (area == NULL)
        return 1;
    _Defer { free(area); }
    return 0;
}

The allocation failure leaves before reaching _Defer. On success, execution reaches the statement and enters its scope without immediately calling free. The release runs on the covered exit from that scope. Replacing _Defer with defer requires the implementation to provide the recommended alternative spelling.

That is a small but concrete portability condition within the proposed extension. Describing both words as universally interchangeable would turn a recommendation into a conformance requirement. The draft explicitly separates the two, so code examples should identify which guarantee they use.

The statement defines a protected region

N3734 defines the scope of a defer statement by analogy with an identifier declared and completed immediately after the deferred body. That definition associates cleanup with the following lexical region. Leaving the enclosing block or the defer's scope through a specified exit runs the deferred body just before that exit.

The cleanup body is an unlabeled statement in the grammar. A compound statement can group several operations there. The revision also permits an attribute-specifier sequence before the defer statement in the enclosing statement grammar. These are grammar details of the extension, separate from the standard library functions that cleanup might call.

Activation still depends on reaching the statement. An unreachable _Defer after a return does not run. This is an important limit for resource ownership because a release operation written near a resource is not automatically associated with every path through the surrounding function. The executed control flow has to reach it.

Multiple deferred bodies execute in reverse order of their statements' appearance within the enclosing block. Nested defer statements obey the same rule within their own scopes. This allows release ordering to follow dependency order when a later acquisition depends on an earlier resource remaining available.

Return values precede cleanup

The draft preserves the rule that a return expression and its conversion to the return type are evaluated before the deferred body executes. Cleanup therefore occurs after computing the value for the caller. That ordering matters when the return expression still uses the resource to be released.

A cleanup body that changes a local object cannot change a return value already computed from that object. This is a consequence of the specified order, not an exception for one scalar type. The draft calls out conversion as part of the computation that precedes cleanup, so conversions with observable consequences belong before the deferred operations too.

The rule also explains why _Defer should not be described as textual substitution immediately before every return token. The feature has a scope and a defined relationship to evaluation. A transformation used to implement it must preserve those semantics, including expressions whose evaluation accesses the resource that cleanup will release.

The draft also connects cleanup to return from the initial call to main. For an int-compatible return type, the described call to exit follows execution of the in-scope deferred statements. This gives that special language-and-library boundary an explicit ordering. It should not be generalized into a claim that an arbitrary direct call to exit runs every active cleanup body. Returning through the language-defined scope and invoking a non-returning function elsewhere are different cases in the draft, with different rules.

Ordinary cleanup has explicit control-flow limits

Jumps cannot enter a deferred body or move from outside its scope into the protected region. Within a deferred body, return, break, continue, and goto cannot leave it. These restrictions prevent activation from being skipped and prevent cleanup from replacing the pending exit with an unrelated control transfer.

Nonlocal jumps and program termination remain separate cases. The draft does not promise to run pending cleanup when a signal terminates the program or when a non-returning function is called. It also specifies nonlocal-jump restrictions and consequences independently of the ordinary block-exit rules. An implementation may provide extra behavior where the draft permits it, but that is additional behavior to identify explicitly.

I find the proposal useful when its guarantees are stated at this level of precision. The November revision gives implementations a required keyword and keeps the cleanup contract tied to lexical scope. A programmer evaluating it can ask whether the source uses the required spelling, reaches the statement, and leaves its scope through a covered exit. Those questions describe the extension's actual requirements without assuming that every termination path unwinds resources.