Skip to content

/glossary/c/undefined-behavior

What is undefined behavior in C?

verified onenvironmentlocal · linux 6.18.44-fc-v230 x64 · GNU bash 5.2.21updated
tool
C
versions
C11, C17, C23
os
any

Undefined behavior (UB) in C is code for which the language standard places no requirements. When a program executes it, the compiler may produce a crash, a wrong result or a program that seems to work, and it may change between compiler versions or optimization levels. Common causes are signed integer overflow, dereferencing a null or dangling pointer and reading outside an array.

Why it matters

The compiler assumes that your program never triggers UB, and it optimizes under that assumption. If a check can only be true after an overflow, the compiler is allowed to remove the check. A bug can therefore stay hidden at -O0 and show up at -O2, as described on gcc -O2 optimization levels. A crash such as a segmentation fault is one possible result of UB, and it is the lucky one, because it is visible.

Common sources

SourceExampleWhy it is undefined
Signed integer overflowINT_MAX + 1The result does not fit in `int`; unsigned arithmetic wraps, signed does not
Null pointer dereference*p where p is NULLNo object lives at address zero
Out-of-bounds accessa[10] for int a[5]The index is outside the array object
Use after freeReading memory after free(p)The object no longer exists
Uninitialized readint x; return x;An automatic variable has no value until it is set
Shift too far1 << 32 for a 32-bit intThe shift count is not less than the width of the type
Division by zerox / 0The result of the operation is not defined

Catch it with the sanitizer

GCC and Clang can add run-time checks with -fsanitize=undefined. The next program adds one to INT_MAX when run with no arguments, which is signed overflow.

bash
cat > ub.c <<'CODE'
#include <limits.h>
#include <stdio.h>
int main(int argc, char **argv) {
    (void)argv;
    int x = INT_MAX;
    x = x + argc;
    printf("%d\n", x);
    return 0;
}
CODE
gcc -fsanitize=undefined -o ub ub.c && ./ub

Output

ub.c:6:7: runtime error: signed integer overflow: 2147483647 + 1 cannot be represented in type 'int'
-2147483648

The sanitizer prints a line of the form file:line:column: runtime error: ... with the operation and the types involved, then lets the program continue unless you add -fno-sanitize-recover. It reports only the code that runs, so it needs tests that reach the faulty path. Warnings from -Wall -Wextra (gcc -Wall -Wextra) catch the cases the compiler can prove at build time.

How to avoid it

  • Check a value before an arithmetic operation that could overflow, or use unsigned types where wrapping is intended.
  • Initialize every variable and check the result of every allocation, as covered in malloc vs calloc.
  • Keep array indices inside the bounds, and pass lengths with pointers.
  • Run tests with the sanitizer on in CI, in a separate build from the one you ship.
  • Do not rely on "it works on my machine": UB is a property of the code, not of one run.

FAQ

Does undefined behavior always crash the program?

No, and that is the danger. The C standard places no requirement on the result, so the program can crash, print a wrong value, appear to work or behave differently after a compiler update or a change in optimization level. A program that ran correctly in a test can still contain undefined behavior.

How is undefined behavior different from unspecified and implementation-defined behavior?

With unspecified behavior the standard allows several outcomes and the program stays valid, such as the order in which function arguments are evaluated. Implementation-defined behavior must be documented by the compiler, such as the size of int. Undefined behavior has no such limit and applies to the whole program.

How do I find undefined behavior in my C program?

Compile with the undefined behavior sanitizer, gcc -fsanitize=undefined or clang -fsanitize=undefined, and run your tests. It checks at run time and prints the file and line of each violation it executes, such as signed integer overflow. Add -Wall -Wextra for the cases the compiler can see without running the code.

Sources