GCC 14 makes checked arithmetic a standard C operation
GCC 14.1 supplies stdckdint.h and bit-precise integer types. Checked arithmetic answers a destination representability question that choosing a wider integer type cannot resolve by itself.
A size calculation can fail before the allocator sees it. If multiplication produces a value that does not represent the intended number of bytes, checking whether allocation succeeded answers the wrong question. The useful addition in GCC 14.1, released on 7 May, is the C23 <stdckdint.h> interface for checked integer arithmetic. It lets code calculate a result and determine whether that result fits its destination in the same operation.
C was my first language, and I value being able to see the operations a program performs. Arithmetic checks should have that same clarity. A standard spelling makes the intended condition easier to identify than a private helper whose promotion and conversion rules need a separate review.
The destination participates in the calculation
The GCC 14 C changes include checked arithmetic and bit-precise integer types. They also introduce the -std=c23 and -std=gnu23 spellings. The manual shipped in the 14.1 source still describes C23 support as experimental and incomplete. Accepting the final standard-mode name is therefore distinct from implementing every part of the standard.
The release's stdckdint.h defines ckd_add, ckd_sub and ckd_mul using GCC's overflow-checking builtins. The result pointer appears first in the standard macros. Each macro returns a boolean indicating that the mathematical result cannot be represented in the destination type. A false result means the stored value represents the calculation correctly.
This is a different question from whether an ordinary intermediate expression fits the type selected by its operands. The destination is explicit. That makes the interface useful when the result must eventually become an allocation size, an index or a field with a particular integer type. The point where the value enters that representation is visible in the call.
The GCC 14.1 builtin documentation describes the calculation as operating in an infinite-precision signed type before converting to the destination. This is a specification of behavior. It does not require the compiler to allocate an arbitrary-precision object at runtime. The manual says GCC attempts to use hardware instructions, including overflow or carry branches, where possible.
The conceptual infinite precision explains why mixed signedness need not be interpreted through the ordinary arithmetic conversions first. The check concerns the mathematical values supplied to the operation. Its destination then determines whether their mathematical result fits. Reviewing that contract is easier than inferring an intended check from several casts surrounding an unchecked expression.
An allocation calculation needs every step checked
This example computes storage for an array followed by a fixed amount of extra data. It is an illustration of the C23 interface, with the checks placed before the resulting size is used.
#include <stddef.h>
#include <stdckdint.h>
#include <stdlib.h>
void *allocate_records(size_t count, size_t record_size)
{
size_t payload;
size_t total;
if (ckd_mul(&payload, count, record_size))
return NULL;
if (ckd_add(&total, payload, (size_t)16))
return NULL;
return malloc(total);
}
The two checks protect different results. A representable multiplication does not establish that adding the extra bytes also fits. Splitting the operations lets a reader associate each rejection with the calculation it protects. The function returns before allocation when either calculation fails. It intentionally leaves application error reporting out of the example.
Sixteen is an illustrative extra-data size, with no claim about a library header or platform layout. In a real format, that value would come from the format definition. Checked arithmetic establishes representability, while the application establishes that the formula describes the intended object. An incorrect record size remains incorrect even when its multiplication fits.
The same distinction applies to resource limits. A result can fit size_t and still exceed an application's permitted allocation. That requires another condition based on the application's policy. Keeping representability separate from the permitted size makes each reason for rejection easier to explain. The compiler cannot derive an organization's resource budget from an integer type.
I regard this as a useful reduction in local arithmetic conventions. It puts the overflow question beside the operation while leaving the application's response explicit. Returning an error, rejecting an input or selecting a different representation are policy choices outside the macro's contract.
The overflow result is still a result
The C working draft N3096, section 7.20, specifies that the macros store a converted result even when they report failure. For an unrepresentable result, the stored value wraps to the destination width. Callers must use the boolean result to decide whether the value is suitable for its intended purpose. Merely invoking the macro does not make subsequent use of that value correct.
This matters when designing a helper API around checked arithmetic. A helper that discards the boolean would remove the information needed to distinguish a valid size from a wrapped one. A helper that returns both values still needs a clear contract about whether the numeric value may be consumed on failure. The allocation example avoids that ambiguity by returning immediately.
The placement of the result pointer also makes mechanical replacement worth reviewing. GCC's underlying builtin accepts its result pointer last, while the standard macro accepts it first. The release header performs that rearrangement. Code migrating directly from a builtin should preserve the intended roles of all arguments instead of relying on their similar names.
A small destination illustrates the difference. Adding one to an unsigned char holding its maximum value cannot produce a representable result in another unsigned char. Storing the same mathematical sum in an adequately ranged unsigned destination can succeed. The operand values have not changed, but the representability question has. This is why the result pointer belongs in the explanation of the operation.
Subtraction gives another useful interpretation. A negative mathematical result does not fit an unsigned destination. Whether that result indicates invalid input depends on the application. A remaining-capacity calculation might reject it, while a different algorithm might require a signed intermediate. The checked operation reports the representability fact and leaves that interpretation to the caller.
A precise integer width solves another problem
GCC 14 also implements _BitInt(N) and unsigned _BitInt(N) on selected targets. These types express a bit width directly. That can make an intermediate representation more explicit when an algorithm needs a width different from the familiar standard integer types. It is a separate facility from checked arithmetic, with different consequences for interfaces.
Selecting a wider representation can make a particular mathematical operation fit. It does not establish that conversion into a narrower destination is valid. A calculation used for an allocation still needs to become an allocation-size type. Likewise, a value stored in a protocol field must fit that field's representation. The eventual destination remains relevant even when earlier calculations have additional range.
The two additions should therefore not be treated as a combined promise that any new integer type can be passed to every new arithmetic macro. N3096 explicitly excludes bit-precise integer types from the checked-arithmetic macro argument categories. Language support for a type and library support for an operation over that type are distinct parts of the implementation contract.
For interoperability, that separation deserves attention. A private calculation can choose an internal type without automatically changing a public interface. Exporting the type introduces additional compatibility questions for every caller. The useful default is to keep the representation decision tied to a demonstrated range requirement and to explain conversions at the boundary.
The standard-mode switch makes intent visible
The new -std=c23 spelling gives build configuration a direct way to state the requested language revision. GCC retains the older c2x spelling, while its documentation deprecates that name. The standard name is useful for communicating intent between a project and its toolchain, but the compiler and library capabilities still determine which programs can be built.
In this release, the checked-arithmetic header is concrete evidence of one implemented facility. Its definitions show exactly which compiler operations provide the behavior. That is a more informative compatibility statement than assuming that acceptance of a command-line option implies availability of every header and language rule associated with C23.
The practical change for arithmetic code is local. A size-producing operation can name its destination and report representability at the calculation site. Review can then focus on whether the formula is correct and whether the application handles rejection appropriately. Those questions remain visible even when the compiler lowers the check to a small hardware-supported sequence.