Safe Arithmetic with Overflow Detection in C
Integer overflow has long been a major source of security
vulnerabilities and undefined behavior in the C programming language.
Historically, developers were forced to rely on complex manual
pre-condition checks or non-portable compiler extensions to catch
arithmetic overflows. With the release of ISO/IEC 9899:2024 (commonly
known as C23), the language officially standardized safe arithmetic
operations through the <stdckdint.h> header,
providing built-in, type-generic macros that detect overflow reliably
across standard integer types.
The Modern Standard:
<stdckdint.h>
Modern C introduces checked integer arithmetic via the standard
library header <stdckdint.h>. This header defines
three primary type-generic macros designed to perform arithmetic
operations and report whether an overflow occurred:
ckd_add(result, a, b): Performs addition (\(a + b\)).ckd_sub(result, a, b): Performs subtraction (\(a - b\)).ckd_mul(result, a, b): Performs multiplication (\(a \times b\)).
Function Signature and Semantics
The macros share a unified invocation pattern:
bool ckd_add(type1 *result, type2 a, type3 b);
bool ckd_sub(type1 *result, type2 a, type3 b);
bool ckd_mul(type1 *result, type2 a, type3 b);- Parameters:
result: A pointer to any standard integer type where the computation's outcome will be stored.aandb: The arithmetic operands, which can be any standard integer types.
- Return Value: Each macro returns a
bool. It returnsfalseif the operation was performed without overflow and the result was correctly represented in*result. It returnstrueif an overflow occurred. - Result Value on Overflow: If an overflow occurs,
the value wrapped according to the target type's modular representation
is stored in
*result, preventing uninitialized memory access while signaling failure.
Mixed-Type Arithmetic Support
A major advantage of <stdckdint.h> is that
type1, type2, and type3 do not
need to be the same integer type. The macros handle mixed signedness and
varying bit-widths safely. For instance, you can add an
unsigned int to a signed long and store the
result in an int8_t. The macro evaluates the
infinite-precision mathematical result and checks whether that exact
value fits within the destination type.
Example Usage
#include <stdio.h>
#include <stdckdint.h>
#include <limits.h>
int main(void) {
int a = INT_MAX;
int b = 1;
int result;
if (ckd_add(&result, a, b)) {
printf("Overflow detected! Operation aborted.\n");
} else {
printf("Result: %d\n", result);
}
return 0;
}Compiler Predecessors: Clang and GCC Built-ins
Prior to C23 standardization, GCC and Clang offered vendor-specific
built-in functions that provided the exact same hardware-accelerated
overflow checking. Modern toolchains implementing C23 typically lower
the <stdckdint.h> macros directly into these
intrinsics:
__builtin_add_overflow(a, b, result)__builtin_sub_overflow(a, b, result)__builtin_mul_overflow(a, b, result)
Like the standard C23 macros, these built-ins are type-generic and map directly to CPU condition flags (such as the carry or overflow flags on x86 and ARM architectures), ensuring optimal execution speed without branching penalties.