Lesson 40
Undefined Behaviour
Undefined Behaviour (UB) in C programming refers to situations where the C standard imposes no requirements on what a program should do when certain operations are executed. Common causes include uninitialized variables, out-of-bounds array access, null pointer dereferencing, use-after-free, signed integer overflow, and division by zero. Understanding and avoiding UB is critical for writing safe, portable, and reliable C programs.
C Track
Save progress as you learn.
44 lessons
Lesson content
Undefined Behaviour in C Programming: A Complete Beginner to Advanced Guide
What is Undefined Behaviour in C Programming?
Undefined Behaviour (UB) in C programming refers to situations where the C language standard does not define what should happen when a program executes certain operations. In other words, once your program triggers undefined behaviour, anything can happen.
The compiler may produce the expected output, produce incorrect output, crash the program, ignore the problematic code, or optimize the code in unexpected ways. Because the behaviour is not specified by the C standard, different compilers, operating systems, or optimization levels may produce different results.
Think of it like driving a car. Traffic laws define what happens at traffic lights. But if someone drives off a bridge, there are no traffic rules describing what happens next. Similarly, when a C program enters undefined behaviour, it has left the rules defined by the language.
Why Does Undefined Behaviour Exist?
C was designed to be fast, portable, and close to hardware. Checking every possible programming mistake at runtime would slow programs down. Instead, the C standard leaves many erroneous situations as undefined, allowing compilers to generate highly optimized machine code.
Real-World Use Cases
Understanding Undefined Behaviour in C programming is essential when working with:
- Operating systems
- Device drivers
- Embedded systems
- Game engines
- Database engines
- High-performance applications
- Compiler development
Prerequisites
Before learning Undefined Behaviour in C, you should understand:
- Variables
- Data types
- Operators
- Functions
- Arrays
- Pointers
- Memory basics
- Loops and conditions
Core Concepts
What is Undefined Behaviour?
Undefined behaviour occurs when a program performs an operation for which the C standard imposes no requirements. Unlike syntax errors, UB usually allows the program to compile successfully.
int x = 5;
printf("%d\n", x / 0);This compiles on many systems, but dividing by zero results in undefined behaviour.
Undefined Behaviour vs Unspecified Behaviour vs Implementation-Defined Behaviour
| Feature | Undefined Behaviour | Unspecified Behaviour | Implementation-Defined Behaviour |
|---|---|---|---|
| Defined by C standard? | No | Multiple valid choices | Compiler must document its choice |
| Predictable? | No | Somewhat | Yes |
| Safe to rely on? | No | Usually not | Yes (if targeting one implementation) |
| Example | Dereferencing a null pointer | Order of function argument evaluation | Size of int |
Common Causes of Undefined Behaviour
1. Accessing Memory Out of Bounds — reading or writing outside an array:
int arr[5];
arr[10] = 100;2. Dereferencing a Null Pointer:
int *ptr = NULL;
*ptr = 5;3. Using Uninitialized Variables:
int x;
printf("%d", x);4. Integer Overflow (Signed):
int x = INT_MAX;
x++;5. Modifying a Variable Multiple Times Without Sequencing:
i = i++;
a = a++ + ++a;6. Division by Zero:
int x = 10 / 0;7. Using Freed Memory:
free(ptr);
printf("%d", *ptr);8. Returning the Address of a Local Variable:
int* fun()
{
int x = 10;
return &x;
}Code Examples
Example 1: Beginner – Using an Uninitialized Variable
#include <stdio.h> // Include standard input/output library
int main() // Program entry point
{
int number; // Declare variable but do not initialize it
printf("%d\n", number); // Undefined Behaviour: reading an uninitialized variable
return 0; // End the program successfully
}The variable number contains an indeterminate value. Reading it causes undefined behaviour.
Example 2: Beginner – Array Out of Bounds
#include <stdio.h> // Standard input/output library
int main() // Program entry point
{
int numbers[5] = {1,2,3,4,5}; // Create an array of five elements
printf("%d\n", numbers[8]); // Undefined Behaviour: index is outside the array
return 0; // Exit program
}Valid indexes are 0 through 4. Accessing numbers[8] reads memory that does not belong to the array.
Example 3: Intermediate – Dereferencing a Null Pointer
#include <stdio.h> // Standard I/O library
int main() // Program entry point
{
int *ptr = NULL; // Create a null pointer
*ptr = 100; // Undefined Behaviour: writing through NULL
return 0; // Exit program
}Example 4: Advanced – Use After Free
#include <stdio.h> // Standard I/O library
#include <stdlib.h> // Memory allocation library
int main() // Program entry point
{
int *ptr = malloc(sizeof(int)); // Allocate memory for one integer
*ptr = 42; // Store a value in allocated memory
free(ptr); // Release the allocated memory
printf("%d\n", *ptr); // Undefined Behaviour: accessing freed memory
return 0; // Exit program
}Example 5: Advanced – Multiple Modifications Without Sequencing
#include <stdio.h> // Standard I/O library
int main() // Program entry point
{
int x = 5; // Initialize variable
int y = x++ + ++x; // Undefined Behaviour: x is modified more than once without sequencing
printf("%d\n", y); // Output is unpredictable
return 0; // Exit program
}Common Mistakes and Pitfalls
| Wrong | Correct |
|---|---|
| int x; printf("%d", x); | int x = 0; printf("%d", x); |
| arr[10] = 5; | Check array bounds before accessing. |
| *NULL = 10; | Verify pointer is not NULL before dereferencing. |
| free(ptr); *ptr = 10; | Set ptr = NULL after free() and avoid using it. |
| i = i++; | Separate operations: i++; |
| return &localVar; | Return dynamically allocated memory or pass an output parameter. |
Best Practices
- Always initialize variables before use.
- Validate array indexes before accessing elements.
- Check pointers before dereferencing.
- Never use memory after calling free().
- Set pointers to NULL after freeing them.
- Avoid modifying the same variable multiple times within one expression.
- Use compiler warnings such as -Wall -Wextra -Wpedantic.
- Enable runtime sanitizers like -fsanitize=address and -fsanitize=undefined during development.
- Test with multiple optimization levels because UB may appear only when optimization is enabled.
- Prefer simpler expressions over clever but hard-to-read code.
When NOT to Use This
Undefined behaviour is never something you should intentionally rely on. Avoid writing code that depends on UB because:
- Different compilers may produce different results.
- Compiler updates may change program behaviour.
- Optimization can remove or rewrite code in surprising ways.
- UB can introduce security vulnerabilities, including buffer overflows and arbitrary code execution.
- Programs become difficult to debug and maintain.
Summary / Key Takeaways
- Undefined Behaviour occurs when the C standard imposes no requirements on what a program should do.
- UB can cause crashes, incorrect results, or seemingly correct execution.
- Common causes include using uninitialized variables, accessing arrays out of bounds, dereferencing null pointers, using freed memory, signed integer overflow, division by zero, returning addresses of local variables, and modifying a variable multiple times without proper sequencing.
- Compilers may optimize based on the assumption that UB never occurs.
- Compiler warnings and sanitizers are valuable tools for detecting many forms of UB.
- Writing clear, standards-compliant code helps prevent undefined behaviour.
FAQ About Undefined Behaviour in C
1. What is Undefined Behaviour in C programming?
Undefined behaviour occurs when a C program performs an operation for which the C standard specifies no required outcome. The program may crash, produce incorrect output, or appear to work correctly.
2. Is Undefined Behaviour the same as a compiler error?
No. Most undefined behaviour is not detected by the compiler. The program often compiles successfully, but its execution is unpredictable.
3. Can Undefined Behaviour work correctly sometimes?
Yes. A program exhibiting UB may appear to work on one compiler or one machine, then fail on another or when compiler optimizations change. This apparent correctness is accidental and should not be relied upon.
4. How can I detect Undefined Behaviour?
You can reduce the risk by enabling compiler warnings (-Wall -Wextra -Wpedantic), using sanitizers (-fsanitize=address, -fsanitize=undefined), running static analysis tools, and thoroughly testing your program.
5. Why doesn't the C compiler prevent Undefined Behaviour?
The C language prioritizes performance and low-level control. By not requiring runtime checks for every invalid operation, compilers can generate faster code. The responsibility for avoiding undefined behaviour is therefore largely placed on the programmer.