CUndefined Behavior

Undefined Behavior

"Undefined behavior" (often abbreviated UB) is a precise technical term in the C standard, not just a vague way of saying "bug." The standard formally defines it as behavior for which the standard imposes no requirements at all. When your code triggers undefined behavior, the compiler is permitted to do absolutely anything: crash, produce wrong output, appear to work perfectly, format your disk, or — the most dangerous outcome of all — optimize based on the assumption that UB never happens, deleting or rearranging code in ways that look nothing like what you wrote.
Undefined behavior is not the same as 'implementation-defined'
Implementation-defined behavior (like the size of an int, or whether char is signed) is unspecified by the standard but each compiler must document and consistently apply some defined behavior. Undefined behavior has no such guarantee — a program can behave differently between two runs, two optimization levels, or even two lines of the same function.
Classic Sources of Undefined Behavior

Source

Example

Signed integer overflow

INT_MAX + 1 -- unlike unsigned wraparound, this is UB, not a defined result

Dereferencing NULL or a dangling pointer

Reading or writing through a pointer after free(), or one that was never initialized

Out-of-bounds array access

int arr[5]; arr[10] = 1; -- no bounds checking exists in C

Using an uninitialized variable

int x; printf("%d", x); -- the value is indeterminate, not guaranteed to be zero

Modifying a variable twice without a sequence point

i = i++ + 1; -- the order of side effects is unspecified

Strict aliasing violations

Accessing the same memory through two unrelated pointer types, e.g. reinterpreting a float* as an int*

Division by zero

int r = x / 0; -- unlike floating-point division, integer division by zero is UB

Shifting by an out-of-range amount

1 << 32 for a 32-bit int -- the shift amount must be less than the bit width

Why It Is So Dangerous
The scariest property of UB is not that it crashes your program — a crash is at least easy to notice. The real danger is that optimizing compilers are legally allowed to assume UB never happens, and use that assumption to simplify your code in ways that break it silently:

C
// A "defensive" NULL check that a compiler may remove entirely.
void process(int *p) {
    int value = *p;      // if p is NULL, this line is already UB

    if (p == NULL) {     // the compiler may reason: "we already
        return;          // dereferenced p above, so if we reached
    }                    // this line, p can't have been NULL" --
                          // and delete this whole check!

    printf("%d\n", value);
}
This is not a hypothetical: real optimizing compilers have removed null checks placed after a dereference for exactly this reason, because the standard says dereferencing NULL is UB and therefore "cannot happen" in a correct program. The fix is always the same — check the pointer before using it, never after.
Signed Overflow: A Concrete Trap

C
#include <limits.h>
#include <stdio.h>

int main(void) {
    int x = INT_MAX;
    int y = x + 1;   // undefined behavior -- NOT guaranteed to wrap to INT_MIN

    printf("%d\n", y);
    return 0;
}
Many programmers assume signed overflow wraps around like it does in some other languages, but C leaves it fully undefined. An optimizing compiler can, and sometimes does, assume x + 1 > x is always true for a signed int and optimize a loop bound check away entirely, producing an infinite loop where none was intended.
Tools That Help Catch UB
  • Compiler warnings (-Wall -Wextra -Wpedantic) catch some UB patterns at compile time -- see the Compiler Warnings page

  • UndefinedBehaviorSanitizer (-fsanitize=undefined) instruments a build to detect UB at runtime and print exactly where it happened

  • AddressSanitizer (-fsanitize=address) catches memory-related UB such as out-of-bounds access and use-after-free

  • Valgrind can detect uninitialized reads, invalid memory access, and leaks without needing to recompile

  • Static analyzers (e.g. clang --analyze, Coverity, cppcheck) can flag suspicious patterns before you ever run the program

There is no such thing as 'harmless' undefined behavior
Code that triggers UB but "seems to work" today is not correct — it is unverified. A new compiler version, a different optimization level, or a different target architecture can all change the observed behavior without any change to your source code. Treat every UB warning or sanitizer report as a real bug to fix, not noise to suppress.