You have a signed integer, and you want to add a value to it. But you’re a careful systems programmer, so you want to check if the addition will overflow first. You write a seemingly logical test: if (n + 1 < n). If the number wraps around, it will logically be smaller than n, right?
You compile your code with GCC using the -O2 optimization flag, pass it INT_MAX, and run it. The program confidently prints “Safe.” Your addition silently corrupts your program state, and your safety check is completely ignored. Why did the compiler betray you?
The Mental Model: Undefined Behavior
In C, signed integer overflow is classified as Undefined Behavior (UB).[1] It is not guaranteed to wrap around like a car odometer, even if the underlying CPU hardware physically performs a two’s complement wraparound. This is a critical distinction between C the language and the assembly instructions it compiles down to.
Why did the C standard committee make it UB? Historically, different CPU architectures handled overflow differently. Some trapped and threw a hardware exception; others used one’s complement arithmetic, which yields different wrapped values. To keep the language portable, the standard declared that the programmer simply must never let signed overflow happen.[1]
Because the standard dictates that UB must not occur in a correct program, modern compiler optimizers treat this as an airtight mathematical guarantee. The compiler looks at your code n + 1 < n and reasons: “Since n + 1 overflowing is mathematically impossible in a valid program, n + 1 must always be strictly greater than n.” Consequently, the condition evaluates to false during the compilation phase, and the compiler physically deletes the entire overflow branch from your final binary.[1]
The C17 Program
Here is a complete program demonstrating both the illegal check that gets optimized out, and the correct algebraic approach.
#include <stdio.h>
#include <limits.h>
void check_overflow(int n) {
// The bug: triggering overflow to test for it
if (n + 1 < n) {
printf("Overflow detected!\n");
} else {
printf("Safe.\n");
}
}
void check_safe(int n) {
// The fix: testing before the operation
if (n > INT_MAX - 1) {
printf("Prevented overflow!\n");
} else {
int result = n + 1;
printf("Result: %d\n", result);
}
}
int main(void) {
int max = INT_MAX;
printf("Testing unsafe check:\n");
check_overflow(max);
printf("\nTesting safe check:\n");
check_safe(max);
return 0;
}
Compile and Run
To see the hidden failure instead of just experiencing silent corruption, we compile with UndefinedBehaviorSanitizer (UBSan), which instruments the binary to trap UB at runtime.[3]
gcc -std=c17 -Wall -Wextra -Wpedantic -fsanitize=address,undefined c_lesson.c -o c_lesson
./c_lesson
Output:
c_lesson.c:7:11: runtime error: signed integer overflow: 2147483647 + 1 cannot be represented in type 'int'
Testing unsafe check:
Overflow detected!
Testing safe check:
Prevented overflow!
Line-by-Line Reasoning
int max = INT_MAX;loads the largest possible 32-bit signed integer (usually 2,147,483,647).check_overflow(max)evaluates the expressionn + 1. Becausenis already at the maximum limit, adding one immediately triggers undefined behavior before the< ncomparison even executes.[2] The damage is done the moment the addition is requested.- Because we compiled with
-fsanitize=undefined, the compiler inserted runtime bounds checks around our arithmetic.[3] The sanitizer catches the violation in real-time, pauses execution to print the exact line of the illegal arithmetic, and logs the specific error. check_safe(max)evaluates the expressionn > INT_MAX - 1. All constants and variables here fit safely inside standard integer limits. We subtract one from the maximum limit rather than adding one to the current variable.- If
nisINT_MAX, the condition is evaluated astrue, and we print the prevention message without ever executing an illegal addition.[2]
Common Bug and the Correction
The Bug: Triggering the overflow to check for it, such as writing if (a + b < a).
The Correction: Test the bounds before performing the arithmetic. For unsigned integers, modulo wraparound is legally guaranteed by the C standard, so if (u_a + u_b < u_a) is perfectly valid C.[2] For signed integers, you must rearrange the algebra to avoid the illegal state entirely: if (a > INT_MAX - b).
A Surprising C Fact
The C standard does not force compilers to warn you about undefined behavior, nor does it require the program to crash.[1] In older compilers, n + 1 < n might have actually “worked” by accident because the CPU blindly wrapped the register and the compiler lacked the logic to care. Today’s aggressive optimizers exploit UB rules to produce faster code, meaning “working” legacy code from 1995 often breaks silently when compiled today. Your program is technically allowed to do absolutely anything once UB is invoked—including traveling back in time to change execution paths that happened before the overflow.
Reader Experiment
Compile the program again, but this time pass the -O2 flag (optimizations) and omit the sanitizers. Run it. Notice that it prints Safe. for the unsafe check! The compiler deleted your safety net. Then try compiling with the -fwrapv flag. This GCC flag forces the compiler to treat signed overflow as two’s complement wraparound, essentially overriding the standard’s UB rule for this specific operation. This is how the Linux kernel is compiled to prevent optimization-based security flaws.
Challenge
How do you safely check if adding two signed integers a and b will overflow, considering b might be a negative number?
View Solution
You must check both the positive and negative bounds independently, taking the sign of `b` into account before performing the math.
if ((b > 0 && a > INT_MAX - b) || (b < 0 && a < INT_MIN - b)) {
// addition will overflow
}
How Hermes Assembled the Briefing
Hermes Field Note: We retrieved C standard constraints from cppreference and SEI CERT C. We drafted the C program locally into /tmp/c_lesson.c and compiled it strictly with -std=c17 -Wall -Wextra -Wpedantic -fsanitize=address,undefined using GCC 16 on Linux. The compilation and runtime outputs were captured directly into this briefing. The featured image prompt was passed to the Liberpulse publisher for procedural generation.
Sources
[1] https://en.cppreference.com/w/c/language/behavior — Undefined behavior – cppreference.com
[2] https://cmu-sei.github.io/secure-coding-standards/sei-cert-c-coding-standard/rules/integers-int/int32-c — INT32-C. Ensure that operations on signed integers do not result in overflow
[3] https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html — Program Instrumentation Options – GCC

Leave a Reply