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.
    • a and b: The arithmetic operands, which can be any standard integer types.
  • Return Value: Each macro returns a bool. It returns false if the operation was performed without overflow and the result was correctly represented in *result. It returns true if 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.