Introduction
The C standard does not pin down the result of every program. It leaves some choices to the compiler on purpose, so that C can run efficiently on very different machines. To answer a C question correctly you must know which of four categories a construct falls into, because only one of them gives a single answer that holds everywhere.
This note defines the categories, lists the cases that matter for GATE, and shows why "it printed 7 on my machine" is not evidence that a program is correct.
Conventions: C11, hosted, gcc on x86-64 Linux (LP64) as the reference platform.
Prerequisites
- Operators, Precedence and Side Effects: sequencing and evaluation order.
- Type Conversions and Integer Arithmetic: conversions and overflow.
Core concepts
The four categories
C11 Section 3.4 defines the terms.
| Category | Meaning | What the implementation must do | Example |
|---|---|---|---|
| Well defined | The standard fixes the result | Produce exactly that result | UINT_MAX + 1u is 0 |
| Implementation-defined | Unspecified behaviour where each implementation documents its choice | Choose one behaviour and document it | size of int; signedness of plain char; -7 >> 1 |
| Unspecified | Two or more possibilities are allowed and no choice need be documented | Pick any allowed possibility, possibly differently each time | order of evaluation of f() + g() |
| Undefined | The standard imposes no requirements | Nothing; any outcome is permitted | INT_MAX + 1, a[10] for int a[10], i = i++ |
A fifth term, locale-specific behaviour (3.4.2), covers choices that depend on national conventions, such as whether islower is true for characters outside a to z. It is rarely relevant to the programs in this subject.
Key distinctions:
- Implementation-defined and unspecified behaviour still produce one of a set of valid outcomes; the program is correct, but portable code must not depend on the choice.
- Undefined behaviour makes the whole execution meaningless, not just one value. A compiler may assume undefined behaviour never happens and optimise on that assumption, so its effect can appear before the line that contains it, or not at all.
Why undefined behaviour is not "garbage value"
A common belief is that INT_MAX + 1 "wraps to INT_MIN". On x86-64 hardware the add instruction does wrap, but the compiler is allowed to reason that a signed addition never overflows. Compiled with gcc at -O2:
int f(int x) { return x + 1 > x; } /* folded to "return 1" */
int g(unsigned x) { return x + 1 > x; } /* must handle wrap-around */f(INT_MAX) returns 1, although with wrapping arithmetic would be negative, while g(UINT_MAX) returns 0, because unsigned arithmetic is defined to wrap. The same source line gives different answers at different optimisation levels. This is precisely what "no requirements" allows.
Catalogue of cases
Undefined behaviour (the GATE-relevant list)
| Construct | Why it is undefined (C11) |
|---|---|
Signed integer overflow, for example INT_MAX + 1 or x * y too large | 6.5 paragraph 5: result not representable |
Division or remainder by zero; INT_MIN / -1 and INT_MIN % -1 | 6.5.5 |
| Shift by a negative count or by at least the width; left shift of a negative value or into overflow | 6.5.7 |
Modifying a scalar twice, or modifying and reading it, without sequencing: i = i++, i++ + i++, a[i] = i++, f(i, i++) | 6.5 paragraph 2 |
| Accessing an array outside its bounds, or forming a pointer more than one past its end | 6.5.6 |
| Dereferencing a null pointer, an uninitialised pointer or a dangling pointer | 6.5.3.2 |
Using an object after its lifetime ends: using a returned pointer to an automatic local after its lifetime ends; use after free | 6.2.4 |
Calling free with an interior pointer or one whose allocation is already deallocated. free(NULL) does nothing; any non-null argument must be the still-live pointer returned by a compatible standard allocation function, including malloc, calloc, realloc or aligned_alloc | 7.22.3.3 |
Modifying a string literal: char *s = "hi"; s[0] = 'H'; | 6.4.5 |
| Reading an uninitialised automatic variable whose address is never taken | 6.3.2.1 paragraph 2 |
A printf conversion specifier that does not match the argument type | 7.21.6.1 |
Using the value of a function call when the function ended without return expr; (other than main) | 6.9.1 |
| Converting a floating value to an integer type when the truncated value is out of range | 6.3.1.4 |
| Using relational pointer comparison outside the cases defined in C11 6.5.8: array elements (including one-past) order by subscript, structure members by declaration, and members of one union compare equal, subject to operand-type constraints; other cases are undefined | 6.5.8 |
With gcc, the runtime sanitizer -fsanitize=undefined reports a diagnostic on signed overflow, division by zero, INT_MIN / -1, both bad shifts, an out-of-bounds array read and a null dereference; -fsanitize=address reports use after free and double free; the unsequenced modifications are reported at compile time by -Wall. Modifying a string literal typically crashes with a segmentation fault on Linux, because literals are placed in read-only memory. To request stopping at the first UBSan error, add -fno-sanitize-recover=undefined or set UBSAN_OPTIONS=halt_on_error=1. Sanitizers do not detect every form of undefined behaviour. None of these observations is a guarantee; they are illustrations of the absence of one.
Implementation-defined behaviour
- The sizes of
short,int,long,long longand pointers;CHAR_BIT. - Whether plain
charis signed (gcc on x86: yes,CHAR_MINis ) or unsigned. - Right shift of a negative signed value (gcc: arithmetic, so
-7 >> 1is ). - Converting an out-of-range value to a signed integer type (gcc: modulo , so
(signed char)200is ). - In C11, which of two's complement, ones' complement or sign and magnitude is used for signed integers (C23 requires two's complement).
- Which integer type
size_t,ptrdiff_tand enumerations use.
Unspecified behaviour
- The order in which operands of most operators, and the arguments of a function call, are evaluated.
- Whether two identical string literals share storage, so whether
"abc" == "abc"is true. - The values of padding bytes in structures.
- Whether a call such as
f() + g()runsforgfirst (no undefined behaviour as long as they do not touch the same object without sequencing).
How to classify a construct
- Is any object modified twice, or modified and read, without sequencing? If yes: undefined.
- Does any signed arithmetic overflow, any division have a zero divisor, or any shift count fall outside ? If yes: undefined.
- Does any pointer go outside its object (beyond one past the end), or is it used after the object's lifetime? If yes: undefined.
- Does the result depend on type sizes,
charsignedness, or the right shift of a negative value? If yes: implementation-defined; use the sizes the question gives. - Check every applicable implementation-defined or unspecified category, not only evaluation order: identical string-literal pooling and padding can also be unspecified. Consult the relevant rule and list the permitted outcomes; documentation is required for implementation-defined choices.
- Otherwise consult the relevant language/library rule. This checklist is not exhaustive: failure to match it is not proof that the result is fixed by the standard.
Worked examples
Example 1. unsigned u = UINT_MAX; u = u + 1; gives 0: well defined.
Example 2. int x = INT_MAX; x = x + 1; is undefined. gcc at -O0 prints here, but the sanitizer reports "signed integer overflow", and at -O2 related comparisons are folded away.
Example 3.
int i = 1;
printf("%d %d\n", i, i++);Undefined: the read of i in the first argument is unsequenced with the modification in the second.
Example 4.
int f(void) { printf("f"); return 1; }
int g(void) { printf("g"); return 2; }
int main(void) { printf("%d\n", f() + g()); }Unspecified: output is fg3 or gf3. Both are correct C.
Example 5.
char a[] = "hi"; a[0] = 'H'; /* well defined: a is a modifiable array copy */
char *s = "hi"; s[0] = 'H'; /* undefined: modifies a string literal */The first prints Hi and sizeof a is 3. See Strings.
Example 6. int arr[3]; int *end = arr + 3; is well defined: forming a pointer one past the end is allowed, and end - arr is 3. Dereferencing *end is undefined.
Example 7.
int *bad(void) { int local = 7; return &local; }Calling bad() is fine; using the returned pointer is undefined, because local no longer exists. gcc warns "function returns address of local variable". See Pointers.
Important properties and observations
- Only well-defined behaviour has one answer on every implementation.
- Undefined behaviour poisons the whole execution, including code that runs before it.
- Unsigned overflow is defined; signed overflow is undefined.
- One past the end of an array is a valid pointer value but not a valid object to read or write.
- A compiler warning is a hint, not the definition: some undefined behaviour produces no warning at all.
- Implementation-defined answers are fixed by the sizes or conventions given in a question.
GATE examination perspective
- If an option reads "undefined behaviour" or "compiler dependent", check the construct with the classification steps above before computing anything.
- Signedness of
char, sizes and right shifts of negatives are implementation-defined; questions normally fix them in the stem ("assume 2-byte integers"). - Returning the address of an automatic local leaves no valid pointer once its lifetime ends; a static-duration local does not end its lifetime on return. Using the returned dangling pointer has undefined behaviour; merely calling the function and ignoring that result is not itself undefined behaviour.
- Questions that print
i++andiin the same call are undefined, whatever the options say. - String-literal modification is a hidden pitfall inside string-manipulation code.
Summary
Sources
- ISO/IEC 9899:2011 (C11), committee draft N1570, (consulted 2026-11-09), Sections 3.4.1 to 3.4.4 (definitions), 6.2.4 (storage durations and lifetime), 6.3.1.4, 6.3.2.1, 6.4.5, 6.5 (paragraphs 2 and 5), 6.5.5 to 6.5.8, 6.9.1, 7.21.6.1, 7.22.3.3, and Annex J (portability issues: J.1 unspecified, J.2 undefined, J.3 implementation-defined behaviour).