Skip to content

Expert · Bits, undefined behaviour and safety · topic 43 of 60 · 30 min

Undefined behaviour

Explanation

Undefined behaviour (UB) means the C standard places no requirements at all on what happens: a crash, wrong output, or apparently correct results that change with the compiler or optimisation level. Common causes are signed overflow, out-of-bounds access, NULL or dangling pointer use, uninitialised reads, unsequenced modifications and over-wide shifts. Optimisers assume UB never happens, which can remove your safety checks. Use -fsanitize=undefined to find it.

Three kinds of "not fully defined"

KindMeaningExample
Implementation-definedEach compiler/platform chooses and documents the behavioursizeof(long); right-shifting a negative int; whether char is signed
UnspecifiedOne of several allowed results; need not be documentedThe order in which f() and g() run in f() + g()
UndefinedNo requirements at all; anything can happenint overflow, a[10] on int a[10], *NULL

Common undefined behaviour

c
int big = INT_MAX;  big + 1;             /* signed overflow (unsigned overflow is defined: it wraps) */
int a[5];           a[5] = 1;             /* out of bounds */
int *p = NULL;      *p = 1;               /* NULL dereference */
int *q = malloc(4); free(q); *q = 1;      /* use after free */
int x;              printf("%d", x);      /* uninitialised read (an indeterminate value) */
int i = 1;          i = i++ + 1;          /* unsequenced modification */
int s = 1 << 32;                          /* shift >= width of int */
printf("%d", 3.0);                        /* wrong printf format */
int r = INT_MIN / -1;                     /* overflow in division */

Why compilers make it worse

Because the standard says these situations cannot happen in a correct program, optimising compilers are allowed to ASSUME they never happen and transform the code accordingly. The results can be surprising: checks for overflow vanish, loops become infinite, and code runs differently at -O0 and -O2.

c
int will_overflow(int x) {
    return x + 1 < x;     /* intended overflow check */
}
/* The compiler reasons: "signed overflow is impossible, so x + 1 < x is always false"
   and may compile the function as: return 0; */

/* A correct check, without overflowing: */
int will_overflow_ok(int x) { return x == INT_MAX; }
/* or compute in a wider type, or use __builtin_add_overflow (GCC/Clang), or C23's ckd_add */
Careful: "It works on my machine" proves nothing for code with undefined behaviour. The same binary can behave differently with different input sizes, and a compiler upgrade or a change from -O0 to -O2 can turn a hidden UB into a visible bug years later. Treat sanitizer reports and -Wall warnings as real defects.

Finding UB

output
gcc -std=c17 -Wall -Wextra -g -fsanitize=undefined,address prog.c -o prog && ./prog
prog.c:12:13: runtime error: signed integer overflow: 2147483647 + 1 cannot be represented in type 'int'
UBSan reports the file, line and the exact problem while the program runs.

Habits that avoid UB

  • Use unsigned or wider types where values can grow (size_t for sizes, int64_t for big counts), and check before arithmetic that could overflow.
  • Check every index against the length and every pointer against NULL.
  • Initialise every variable.
  • One modification of a variable per expression.
  • Match printf formats to argument types (gcc checks these with -Wformat).
  • Test with sanitizers and multiple optimisation levels.
Think of it like this: Undefined behaviour is like driving through a red light because "the road is usually empty at night". The traffic rules do not say what happens next; most nights nothing does, until the one night it ends in disaster. Compilers are like other drivers who assume everyone obeys the signals.
Tip: John Regehr's and the LLVM project's articles "What every C programmer should know about undefined behavior" are well-known, free deep dives into how compilers exploit UB.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Undefined behaviour · 1/6

Undefined behaviour

In this lesson you will learn what undefined behaviour is and why it is dangerous.

Prefer a video by a teacher? Find free videos on “undefined behavior in c explained compiler optimization sanitizer” on YouTube ↗

In real life: An overflow check that the compiler removed

A billing system added a check if (total + amount < total) to detect overflow of an int running total. In a release build with optimisation, the compiler removed the check entirely (signed overflow "cannot happen"), and a very large corporate invoice silently wrapped to a negative number. The fix used a 64-bit total and an explicit limit check before adding.

c
#include <stdint.h>
int add_amount(int64_t *total, int64_t amount) {
    if (amount > 0 && *total > INT64_MAX - amount) return -1;   /* would overflow: refuse */
    *total += amount;
    return 0;
}

Practice (0/3 done)

Task 1 · Question

Which is NOT undefined behaviour?

Task 2 · Question

Why might a compiler turn return x + 1 < x; into return 0;?

Task 3 · Practice

Write a C program containing five different kinds of undefined behaviour (signed overflow, out-of-bounds read, uninitialised read, shift by 32, use after free). Compile it with -g -fsanitize=undefined,address and record which ones are reported and with what messages. Then fix each one and confirm a clean run.

Quick quiz

Question 1

1.What does the C standard require when undefined behaviour occurs?

Question 2

2.Whether plain char is signed is…

Question 3

3.Which tool reports signed overflow at run time?

Question 4

4.The order of evaluation of f() and g() in f() + g() is…

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

What does the C standard require when undefined behaviour occurs?

Answer: Nothing at all

No requirements: anything may happen.

Whether plain char is signed is…

Answer: Implementation-defined

Each compiler/platform documents it.

Which tool reports signed overflow at run time?

Answer: -fsanitize=undefined (UBSan)

UBSan.

The order of evaluation of f() and g() in f() + g() is…

Answer: Unspecified

Either may run first.

Which tool runs your tests and detects many kinds of undefined behaviour?Related

Answer: Miri

Miri interprets the program and checks memory rules.

From Unsafe Rust: what it is and is not

What is a dangling pointer?Related

Answer: A pointer to memory that was freed or went out of scope

Using it is undefined behaviour.

From NULL, dangling pointers and pointers to pointers

What is a buffer overflow?Related

Answer: Writing past the end of an array into neighbouring memory

It corrupts other data.

From Buffer overflows and safe string handling

Which flag combination helps catch out-of-bounds and undefined behaviour while practising with GCC?Related

Answer: -fsanitize=address,undefined -D_GLIBCXX_DEBUG

Sanitizers and the debug STL report errors at the faulty line.

From Classic C++ pitfalls in contests and coding rounds

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 2 more topics in “Bits, undefined behaviour and safety” before its exam: Secure and defensive C, Function pointers and callbacks.
  2. Module 9 exam: Bits, undefined behaviour and safety · 12 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, Data structures in C. 60 topics left in the course before the final exam.

← Previous: Masks and flags