Undefined behaviour (UB) means the standard places no requirements on what happens. The program may crash, print garbage, work by luck — or be “optimised” in surprising ways because the compiler assumes UB never happens.
| Kind | Meaning | Example |
|---|---|---|
| Undefined | Anything can happen; the whole program is invalid | Signed overflow, out-of-bounds access, NULL dereference |
| Unspecified | One of several valid outcomes, not documented | Order of evaluating function arguments |
| Implementation-defined | Documented choice of the compiler/platform | sizeof(int), signedness of char, right shift of negatives |
| Locale-specific | Depends on the current locale | isalpha for non-ASCII bytes |
The catalogue
| Undefined behaviour | Example |
|---|---|
| Signed integer overflow | INT_MAX + 1 |
Division or modulo by zero; INT_MIN / -1 | x / 0 |
| Shift by negative or ≥ width; left-shift of negative | 1 << 32, -1 << 1 |
| Array access out of bounds (even reading) | a[5] for int a[5] |
| Pointer arithmetic beyond one-past-the-end | p + n + 1 |
| Dereferencing NULL, dangling or misaligned pointers | *(int *)NULL |
| Reading an uninitialised automatic variable | int x; printf("%d", x); |
| Modifying a variable twice between sequence points | i = i++; |
Modifying a string literal or a const object | char *s = "hi"; s[0] = 'H'; |
| Strict-aliasing violation | Reading a float through an int * |
| Use after free, double free | see Memory Bugs |
| Falling off a non-void function whose value is used | missing return |
Mismatched printf format and argument | printf("%d", 3.0) |
| Converting an out-of-range float to an integer | (int)1e20 |
| Data race between threads | two threads write one variable without sync |
Signed overflow
#include <stdio.h>
#include <limits.h>
int main(void) {
int x = INT_MAX;
x = x + 1; /* UB — not guaranteed to wrap to INT_MIN */
printf("%d\n", x);
return 0;
}main.c:6:11: runtime error: signed integer overflow: 2147483647 + 1 cannot be represented in type 'int'Because overflow “cannot happen”, an optimiser may turn if (x + 1 > x) into if (1) and for (int i = 0; i <= n; i++) into a loop that assumes n < INT_MAX. Check before the operation:
#include <stdio.h>
#include <limits.h>
#include <stdbool.h>
bool safe_add(int a, int b, int *out) {
if ((b > 0 && a > INT_MAX - b) || (b < 0 && a < INT_MIN - b)) return false;
*out = a + b;
return true;
}
int main(void) {
int r;
printf("%d\n", safe_add(INT_MAX, 1, &r));
if (safe_add(40, 2, &r)) printf("%d\n", r);
printf("%d\n", __builtin_add_overflow(INT_MAX, 1, &r)); /* GCC/Clang builtin (C23: ckd_add) */
return 0;
}0
42
1Division by zero
#include <stdio.h>
int divide(int a, int b) { return a / b; }
int main(void) {
printf("%d\n", divide(10, 0));
return 0;
}main.c:3:37: runtime error: division by zeroStrict aliasing & type punning
The compiler assumes pointers to different types don’t point to the same memory (except char *). To reinterpret bytes, use memcpy — it compiles to a single move:
#include <stdio.h>
#include <string.h>
#include <stdint.h>
int main(void) {
float f = 1.0f;
uint32_t bits;
memcpy(&bits, &f, sizeof bits); /* well-defined type punning */
printf("0x%08X\n", (unsigned)bits); /* IEEE 754: sign 0, exponent 127, mantissa 0 */
return 0;
}0x3F800000Living with UB
Compile with
-Wall -Wextraand fix every warning.Test with
-fsanitize=undefined,address(andthreadfor concurrency).Use unsigned types for bit manipulation and wrap-around arithmetic.
Validate inputs before arithmetic, indexing and shifting.
-fwrapvmakes signed overflow wrap (GCC/Clang) — a band-aid, not a fix.
What is undefined behaviour and why does C have it?
Why can undefined behaviour 'time travel'?
Is i = i++ undefined?