April 27, 2021·6 min read·engineeringcgccdiagnosticsmemory

GCC 11 lets C declarations describe resource ownership

GCC 11.1 extends allocation attributes with matching deallocators and adds C2x diagnostics. These declarations let the compiler recognize ownership mistakes across an API boundary.

A resource-owning C function can return a pointer whose type says almost nothing about its release rules. Two APIs may return the same pointer type while requiring different cleanup functions. Documentation can explain the distinction, but the compiler needs a machine-readable relationship before it can diagnose a mismatched release. GCC 11 extends its allocation annotations to express that relationship in a declaration.

The April 27 announcement releases GCC 11.1 with C2x improvements among its language changes. The GCC 11 changes describe the extended malloc attribute and the associated mismatch warnings. Alongside that work, the C front end gains [[nodiscard]] and __has_c_attribute. These additions let headers communicate more of an API's intended use to a caller's compiler.

I regard explicit ownership rules as essential to writing C well. The language's small size does not remove the need to know which function releases an object. An annotation that states that relationship next to the allocation declaration gives the toolchain information that otherwise exists only in prose or in the implementation.

Allocation has more than one property

The GCC 11.1 attribute manual distinguishes two uses of malloc. The argument-free form describes an aliasing property of the returned pointer. It says that the result cannot alias another pointer valid when the function returns, and that the returned storage contains no pointers to valid objects. That is information the optimizer can use.

The form with arguments identifies a suitable deallocation function. An optional positional index identifies the parameter through which that function receives the pointer to release. This is separate information. A declaration that needs to assert both properties uses both forms of the attribute. Treating them as interchangeable would confuse an optimization promise with an ownership association.

The manual's distinction also explains why a familiar allocation-like name is insufficient evidence for the argument-free attribute. It specifically excludes realloc from that property's examples because returned storage may contain pointers to existing objects. A wrapper must satisfy the documented semantics before its author attaches an optimization annotation. The compiler is entitled to use the declaration, so an inaccurate promise can affect generated code.

A schematic API for newly allocated byte storage can describe both relationships as follows. The declarations assume the allocator really provides fresh storage with the documented nonaliasing property.

#include <stddef.h>

void release_bytes(void *ptr);

void *allocate_bytes(size_t count)
    __attribute__((malloc, malloc(release_bytes, 1)));

The release declaration appears first because the attribute must name a function the compiler already knows. The index is one-based and identifies the pointer parameter of release_bytes. An API with an additional context parameter would need to identify its pointer argument accordingly. The association belongs to the actual signature, so a copied attribute with the wrong position would misdescribe the API.

A matching function is part of the contract

The extended annotation lets GCC recognize that a pointer from one allocation API has reached an unrelated release function. -Wmismatched-dealloc, enabled by default, diagnoses mismatched allocation and deallocation calls. The release notes also describe expanded -Wfree-nonheap-object detection for pointers that did not come from allocation functions.

This is useful at an interface boundary where the implementation is absent. A caller may see only the header, but the declaration can still identify the matching release function. That makes the header more informative without requiring a distinct pointer representation for every resource family. The pointer's C type remains unchanged while the compiler receives an additional relation between functions.

The versioned manual uses stream-opening functions to illustrate the point. It associates fclose with several stream constructors and pclose with popen. Both release functions accept a stream pointer, but they are not substitutes in the described contract. A type-correct argument is therefore only one part of a correct call.

The implementation has a cost worth recording. GCC 11.1 does not accept the deallocator-associating form on inline functions. The manual explains that inlining only one member of a pair could create an apparent mismatch. Using the annotation also prevents the allocator and deallocator from being expanded inline. That restriction affects how a library can structure its wrappers.

This is a more specific tradeoff than saying annotations are free documentation. They supply diagnostic knowledge, but this form changes inlining eligibility. A header author choosing it must account for that behavior, especially if the API currently consists of inline wrappers. The relationship is useful precisely because GCC treats it as information about calls, and the compiler must retain enough of those calls to interpret the association consistently.

Path analysis uses the same declarations

The static analyzer also consumes the allocation pairs. With -fanalyzer, the manual describes diagnostics for leaks and double release, as well as uses after release. These checks concern possible execution paths. Their scope differs from recognizing an immediately mismatched function call because they must reason about the sequence of operations on the pointer.

Failure behavior matters to that reasoning. The analyzer considers the possibility that an allocation function returns null and can diagnose a subsequent unchecked dereference or a call requiring a non-null argument. If a function always returns non-null, the separate returns_nonnull attribute expresses that guarantee. Merely declaring a matching deallocator does not assert that allocation always succeeds.

Null handling on release is another independent property. The analyzer assumes deallocators tolerate null. When that is untrue, the manual recommends a nonnull annotation so it can diagnose a possible null argument. An ownership contract therefore has several components. A correct release pairing does not establish the allocator's failure policy or the release function's accepted inputs.

I like the precision of those distinctions. C APIs often accumulate conventions that callers are expected to infer from names. Describing each relevant property separately makes the declaration reviewable. It also avoids treating a single allocation label as a substitute for understanding what the function actually guarantees.

C2x adds a result-use annotation

GCC 11's C support includes [[nodiscard]] under the developing C2x language revision. A result-use annotation addresses a different API mistake from mismatched deallocation. A caller may invoke a function correctly and then discard the value needed to determine whether the operation succeeded. Marking the declaration can make that discarded result diagnosable.

The release provides -std=c2x and -std=gnu2x for the developing language mode. Some additions also work as extensions in earlier modes, but that statement does not make every C2x feature portable to every compiler. The new __has_c_attribute operator gives code a direct way to ask about attribute support. Header compatibility needs to account for compilers that do not recognize that query operator itself.

The practical scope of nodiscard remains limited. A diagnostic about an ignored result cannot establish that the caller interpreted the result correctly. A program might assign it to a variable and then make the wrong decision. The annotation identifies a particular misuse pattern, while the API still needs an intelligible return-value contract.

Ownership and result use can also overlap without becoming the same rule. Discarding an allocation result can lose the only reference through which a caller could release the allocation. An ignored status result may instead hide failure while leaving resource ownership unchanged. This distinction is useful when designing annotations for a header because the compiler needs a statement about the particular function, rather than an assumption that all returned values have the same purpose.

A deallocator association likewise describes how to release a resource without deciding when release is required. The caller's control flow still determines whether ownership has ended or has passed elsewhere. Path-sensitive analysis can investigate some consequences of that flow, but the basic declaration does not specify every transfer protocol an API might implement. That explains why the release combines direct mismatch warnings with analyzer checks instead of presenting either one as a complete ownership system.

Array-parameter warnings add another kind of information check. GCC 11 introduces -Warray-parameter for inconsistent ordinary-array declarations and -Wvla-parameter for inconsistent variable-length-array forms or bounds. Both are enabled by -Wall, and the release notes connect them to detecting likely calls with undersized arrays. Consistency across declarations gives diagnostics a better basis for understanding the intended input.

These changes make declarations carry more facts that matter to callers. A pointer type still cannot express every ownership or size constraint, but GCC can use explicit relationships attached to the declaration. For a public C header, the resulting work is concrete. Allocation families need accurate release associations, result-use requirements need deliberate annotations, and repeated parameter declarations need to agree about their bounds. Each statement supplies a specific fact the compiler can check against a call.