Undefined Behavior in C: Common Causes, Examples, and Fixes
Learn to find the first undefined operation in common C programs, repair the missing invariant, and verify the corrected result with worked examples.
KnowledgeGate Team
Exam prep & CS education

A C program can compile, run, and display output even though the language defines no result for what it did, making undefined behavior dangerous for learners.
A familiar-looking value from one run is not proof: once a program performs an undefined operation, no numeric output can be promised.
The most common cases can be classified, traced, repaired, and tested by checking bounds, lifetimes, sequencing, arithmetic ranges, and library contracts.
What undefined behavior in C actually means
Undefined behavior begins at an operation for which C imposes no required result. A crash, a stale value, a surprising optimization, or apparently correct output can all be observed, but none is guaranteed. A compiler is not generally required to diagnose every undefined operation.
Do not put every portability issue into the same bucket:
Category | Example | What C requires |
|---|---|---|
Undefined | Dereferencing | No required result |
Implementation-defined | Whether plain | The implementation chooses and documents it |
Unspecified | Whether independent | Either order is allowed; the sum remains defined if there are no conflicting side effects |
For a memory-first treatment of addresses, pointer arithmetic, and tracing, use the Pointers in C for GATE guide. Undefined-behavior diagnosis starts at the first operation whose precondition fails, then repairs that condition before execution.
Fully worked example: the one-past array read
Trace this program first:
#include <stdio.h>
int main(void) {
int score[3] = {12, 24, 36};
int sum = 0;
for (size_t i = 0; i <= 3; ++i) {
sum += score[i];
}
printf("%d\n", sum);
return 0;
}Trace until the invalid access:
i = 0reads12, sosum = 12.i = 1reads24, sosum = 36.i = 2reads36, sosum = 72.i = 3attempts to readscore[3].
A pointer one position past an array may be formed for comparison, but it may not be dereferenced. Stop here: undefined behavior. There is no C-defined final sum or output.
Use size_t n = sizeof score / sizeof score[0]; with i < n. It visits indices 0, 1, and 2, so it prints exactly 72. Before a[i], prove 0 <= i < element_count, or simply i < element_count when i is unsigned.
![Memory diagram of int score[3] = {12, 24, 36} with running sums 12, 36, 72 and a dashed one-past score + 3 slot the loop must not read.](https://cdn.knowledgegate.ai/blog-assets/blog_asset_1784467986873_ab1bqn.jpg)
Signed overflow, zero divisors, and invalid shift counts
For int x = INT_MAX; int y = x + 1;, the addition is undefined because its mathematical result is outside the range of int. With a common 32-bit int, INT_MAX is 2,147,483,647, while the wanted result is 2,147,483,648. Portable code reads INT_MAX from <limits.h>. Check before evaluating: reject when x == INT_MAX; otherwise compute y = x + 1. Thus x = 41 gives y = 42, while x = INT_MAX takes the rejection branch.
For int marks = 84, groups = 0; int each = marks / groups;, integer division by zero is undefined. Check groups != 0 first. With groups = 7, each = 84 / 7 = 12; with zero, report an application error without executing /.
Finally, consider unsigned bit = 1u; unsigned width = sizeof bit * CHAR_BIT; bit << width;. A shift count equal to the promoted left operand's width is undefined. On a 32-bit unsigned, width = 32. Require shift < width; safe shift = 5 gives 1u << 5 = 32.

Dangling pointers and object lifetime errors
Consider int *bad(void) { int answer = 42; return &answer; }, followed by printf("%d\n", *bad());. The local object answer stops existing when bad returns. The returned pointer therefore provides no access to a live int, and dereferencing it is undefined. No printed value is defined.
Give the caller ownership instead: void set_answer(int *out) { *out = 42; }. Then int answer; set_answer(&answer); printf("%d\n", answer); prints exactly 42.
Heap storage has the same lifetime rule. Suppose a program allocates one int, checks that allocation succeeded, assigns 73, calls free(p), and then evaluates printf("%d\n", *p);. The last dereference is undefined even if the bytes appear unchanged. Finish every read before free, make ownership explicit, and never dereference the pointer afterwards. Setting p = NULL is a useful defensive aid, but it does not make dereferencing valid. Use Dynamic Memory Allocation in C for allocation, realloc, and deallocation patterns. A use-after-free trace needs the lifetime boundary: free ends access to the object, so the next dereference is invalid.
Ask during review: “Is the pointed-to object alive for the entire access?” Local-scope exit, free, and storage invalidated by a successful realloc are important lifetime boundaries.
Unsequenced side effects and broken library contracts
In int i = 4; int result = i++ + i;, the modification and other access to i are unsequenced, so the expression has undefined behavior. Separate the operations: int old = i; ++i; int result = old + i;. Now old = 4, i = 5, and result = 9.
Library and language contracts matter too:
char *label = "GATE"; label[0] = 'L';attempts to modify a string literal and is undefined. Withchar label[] = "GATE"; label[0] = 'L';, the writable array contains exactly"LATE".long attempts = 50000L; printf("%d\n", attempts);mismatches the variadic argument and format.printf("%ld\n", attempts);prints50000.Given
char text[6] = "ABCDE",memcpy(text + 1, text, 4)violatesmemcpy's non-overlap requirement. Usememmove(text + 1, text, 4)instead; the array becomes"AABCD".
The general lesson is to satisfy every standard-library precondition, not merely call a familiar function.
A repeatable diagnostic and repair workflow
For one isolated example at a time, build with:
cc -std=c17 -O1 -g -Wall -Wextra -Wpedantic -Wconversion -fsanitize=address,undefined -fno-omit-frame-pointer ub.c -o ub-demoCompiler and sanitizer availability and messages vary. Neither clean warnings nor a clean sanitizer run proves the absence of every UB category.
Use a fixed order: reduce the case to a minimal reproducer, identify the first invalid operation, state the missing invariant in plain language, enforce it before the operation, run the corrected program, then test boundary values. Compare -O0 and -O2 only to expose a symptom, never to call one build correct.
The repaired examples produce these defined results: array sum 72, increment 42, division 12, shift 32, caller-owned answer 42, sequenced result 9, writable label LATE, formatted count 50000, and overlapping move result AABCD. During timed practice, apply the same first-invalid-operation rule before attempting an output.
Traps and three exercises with answer keys
Classify
int a[2] = {7, 11}; printf("%d\n", a[2]);. Answer: undefined, becausea[2]dereferences the one-past position. Read onlya[0]ora[1], or deliberately change the required data and array size.Classify
int q = 3; int r = q++ + q;. Answer: undefined because the modification and access are unsequenced. Rewrite it asint old = q; ++q; int r = old + q;. Thenold = 3,q = 4, andr = 7.Compare
UINT_MAX + 1uwithINT_MAX + 1. Answer: unsigned arithmetic wraps moduloUINT_MAX + 1, so the first value is defined as0. The signed overflow in the second expression is undefined.
Avoid the classic wrong answers “it always wraps” and “the compiler will always catch it”.
How exams and interviews test undefined behavior
Stable questions ask you to identify the first undefined operation, classify UB versus implementation-defined or unspecified behavior, choose a safe rewrite, or explain why an optimizer assumes valid code avoids signed overflow and invalid dereferences.
If asked for output after UB, answer “no C-defined output”, not a value from one compiler. Stop at the first broken rule and repair its precondition.
The short version
Prove array bounds, keep objects alive, separate side effects, validate divisors and shift counts, guard signed ranges before arithmetic, and match every library contract.
Build the full foundation in the C Language course. Use Coding For Placements for assessment practice, or browse the Coding & DSA course pathway.
Keep learning

C Operators and Expressions Explained: Precedence, Types and Worked Traces
Learn a repeatable way to parse and evaluate C expressions. Follow typed calculations, bitwise and short-circuit traces, and common exam traps.

Introduction to Flowcharts: Symbols, a Worked Example and Exam Traps
Learn how flowchart symbols express sequence, decisions and loops. Then trace a complete even-sum example, translate it into C and repair common logic defects.

Structures in C: Syntax, Member Access and Worked Examples
Learn how C structures group related values, then trace object and pointer access through complete, implementation-safe examples.

Functions in C: Syntax, Parameters and Worked Examples
Learn how C declarations, calls, and definitions fit together. Compile two complete programs, follow their values, and trace composition and recursion.