Evaluation order, sequence points and undefined behaviour
The four kinds of behaviour the C standard distinguishes, the sequence-point rule that makes expressions like i = i++ + ++i meaningless, and how to tell a question that has an answer from one that has none.
Why it exists
Look at i = i++ + ++i;. Students try to compute a number, and some textbooks even give one. The honest answer is that the C language gives no meaning to this line at all. The standard deliberately leaves the order of some operations open, and it forbids certain combinations altogether, so that compilers can optimise freely. If you do not know which situations are defined, implementation-defined, unspecified or undefined, you cannot tell whether a GATE question has an answer.
Prerequisites
Precise definitions
C11 section 3.4 defines three kinds of behaviour that are not fully determined by the standard (implementation-defined, undefined, unspecified). "Well-defined" is just the everyday word for everything else. Learn the vocabulary exactly:
- Well-defined. The standard says what happens, so the observable result is the same on every conforming compiler.
- Implementation-defined. The standard allows several results, but each compiler must document its choice. Examples: the size of
int, whether plaincharis signed, the result of right-shifting a negative number, the result of converting an out-of-range value to a signed type. - Unspecified. The standard allows two or more results and does not require the compiler to say which, nor to be consistent. The program is still valid and some allowed result occurs. Example: the order in which the arguments of a function call, or the operands of
+, are evaluated. - Undefined behaviour (UB). The standard imposes no requirements at all. The compiler may ignore the situation with unpredictable results, behave in a documented way, or terminate with a diagnostic. In practice the program may crash, print anything, appear to work, or behave differently after you change the compiler flags. Examples: signed overflow, division by zero, out-of-bounds access, dereferencing a null or dangling pointer, modifying a string literal, modifying an object twice without sequencing.
Sequence point. A point in the execution at which all side effects (changes to variables) of everything evaluated so far have finished, and no side effect of anything later has begun. The sequence points you need:
- At the end of a full expression: the
;of an expression statement, an initialiser, the controlling expression ofif,switch,while,do, each of the three expressions in afor (...)header, and areturnexpression. - After the first operand of
&&,||, the?:condition, and the comma operator,. - After all arguments of a function call have been evaluated, just before the function runs.
- At the end of a full declarator in a declaration (
int a = 1, b = a;is fine).
Note that the commas separating function arguments are not the comma operator, so they give no sequencing between the arguments.
The rule. The classic form (C89 and C99, and the one GATE questions use): between two sequence points, a scalar object may be modified at most once, and if it is modified, any other read of it must be only to compute the value to be stored. Break it and the behaviour is undefined. C11 6.5p2 states the same idea with sequenced-before: if a side effect on a scalar object is unsequenced relative to another side effect on the same object, or to a read of its value, the behaviour is undefined. If the operands can be evaluated in several allowed orders, it is undefined when the clash occurs in any of the orders.
How it works
Below are real programs, each compiled and run for real at several optimisation levels and under the sanitizers. Pick a tab, read the source, and look at what each setting did. Where the settings agree, that proves nothing; where they disagree, or a sanitizer objects, you are seeing undefined behaviour at work.
The same program, run for real
Real runs on this machineOrder of evaluating function arguments and operands
#include <stdio.h>
int f(void) { printf("f "); return 1; }
int g(void) { printf("g "); return 2; }
int main(void) {
printf("%d %d\n", f(), g());
int s = f() + g();
printf("%d\n", s);
return 0;
}What the compiled program did
g f 1 2 f g 3
gcc -O0
g f 1 2 f g 3
gcc -O1
g f 1 2 f g 3
gcc -O2
g f 1 2 f g 3
gcc -O3
The GATE answer
Unspecified. Each order is allowed, and the standard does not even require the compiler to be consistent. A well-posed GATE question will not depend on it. Here every setting happened to agree, which proves nothing.
Some things to see:
- Argument order. For
printf("%d %d\n", f(), g())this gcc rangbeforef, yet forf() + g()it ranffirst. Both are allowed. Another compiler may do the opposite, and so can the same compiler tomorrow. i = i++ + ++i. gcc itself warns that "operation on 'i' may be undefined" and, in this run, printed the same number at every level. That agreement is an accident of one compiler and carries no guarantee.- Signed overflow printed a wrapped number without the sanitizer. Under UBSan it is reported as a runtime error, because the standard says the program already broke the rules.
- Array out of bounds printed
3at one level and32767at another, and AddressSanitizer stops it. The element read is simply whatever sits in memory next to the array.
Worked examples
EX 1Classify the behaviour
Easy| Situation | Category |
|---|---|
INT_MAX + 1 | undefined (signed overflow) |
5 / 0 | undefined |
a[10] for int a[5] | undefined |
*p with p == NULL | undefined |
s[0] = 'x' for char *s = "abc" | undefined |
i = i++ + 1 | undefined (two unsequenced modifications of i) |
sizeof(int) | implementation-defined |
-8 >> 1 | implementation-defined (arithmetic shift in gcc) |
(signed char)200 | implementation-defined |
Order of f() and g() in f() + g() | unspecified |
Order of the arguments in h(f(), g()) | unspecified |
i++ && i++ | well-defined |
x << 40 for a 32-bit int | undefined |
free(p); free(p); | undefined |
EX 2&& is a sequence point
Medium#include <stdio.h>
int main(void) {
int i = 0;
if (i++ == 0 && i++ == 1) printf("both ");
printf("%d\n", i);
return 0;
}Output:
both 2Why it is fine. There is a sequence point after the left operand of &&. So i++ == 0 is evaluated completely, including the increment (it yields 0, so the comparison is true, and i becomes 1), and only then is i++ == 1 evaluated (it yields 1, so the comparison is true, and i becomes 2). Compare this with i++ + i++, which has no sequence point between the two modifications.
EX 3The comma operator sequences its operands
Medium#include <stdio.h>
int main(void) {
int a = 1;
int r = (a++, a++, a);
printf("%d %d\n", r, a);
return 0;
}Output:
3 3Step 1. The comma operator evaluates its left operand, finishes all its side effects, and only then evaluates the right one. So a goes 1, 2, 3, and the whole bracketed expression has the value of the last operand, a, which is 3.
Step 2. The commas separating function arguments are not this operator, so they give no ordering between the arguments. (The commas in a declaration such as int a = 1, b = 2; are not the operator either, but C11 does put a sequence point at the end of each full declarator.)
EX 4The ?: condition is a sequence point
Medium#include <stdio.h>
int main(void) {
int k = 0;
int r = (k++ ? 100 : -k);
printf("%d %d\n", r, k);
return 0;
}Output:
-1 1Reading it. The condition k++ is fully evaluated first: its value is 0 (false) and k becomes 1. Only then is the chosen branch -k evaluated, and it sees the new k = 1. Hence . The branch not chosen is never evaluated.
EX 5Unspecified order with a well-defined result
GATE level#include <stdio.h>
int sq(int x) {
return x * x;
}
int main(void) {
int r = sq(2) + sq(3);
printf("%d\n", r);
return 0;
}Output:
13The point. The order in which sq(2) and sq(3) are called is unspecified, but it cannot be observed: the functions have no side effects, and addition gives the same value either way. So the answer, 13, is certain. Compare the earlier runs where f and g print: there the order is visible, and the answer to "what is printed" depends on an unspecified choice.
How to decide. Ask two questions. Does any order-dependent side effect exist (a print, a modification of a shared variable)? If not, the order does not matter. If so, the output is not determined by the language.
EX 6i = i++ + ++i, and what to answer
GATE levelTake int i = 5; i = i++ + ++i;. The variable i is modified by i++, by ++i and by the assignment, all without a sequence point in between, and it is read in between as well. By the rule above, the behaviour is undefined. The run above shows this gcc printing 12, but that is not a property of C.
What to answer. Of the options, "undefined" is the answer that matches the C standard; "compiler dependent" or "cannot be determined" usually express the same idea, so read the options carefully. If every option is a number, compare the question's wording and options against the official answer key. (needs verification: some older GATE papers contain questions of this style whose official key assumes right-to-left evaluation of arguments. Check each such question individually against the key, and prefer "undefined" whenever it is offered.)
Cheat box
Cheat box
- Undefined: no requirements at all (signed overflow,
/ 0, out of bounds, null or dangling dereference, modifying a literal, modifying a variable twice between sequence points, bad shifts). - Unspecified: several results allowed, none documented (order of evaluating operands and arguments).
- Implementation-defined: several results allowed, documented by the compiler (
sizeof(int),charsignedness,>>of negatives). - Sequence points:
;, after the first operand of&&||?:and the comma operator, after the arguments and before the call. - Rule: between two sequence points, modify an object at most once, and read it only to compute the new value.
- Fine:
i++ && i++,(a++, a++, a),x = x + 1,y = x++ + 5. - Not fine (undefined in C):
i = i++,i++ + i++,a[i] = i++,f(i, i++),i = ++i + 1. - Precedence is not evaluation order.
Traps and GATE patterns
Connections
Across subjects
Within this subject
The same idea that "the language does not fix this order" appears in compilers as the evaluation orders of attributes in syntax-directed definitions. Many undefined-behaviour examples (out-of-range indexing, use after free) come from arrays and dynamic memory.
PYQs
2 previous year questions from your book. I re-solved each one myself (2 by running or simulating, 0 by reasoning only); the book's key is shown next to my answer. All PYQs
Consider the following ANSI C program.
#include <stdio.h>
int main()
{
int i, j, count;
count = 0;
i = 0;
for (j = –3, j <= 3; j++)
{
if ((j >= 0) && (i++))
count = count + j;
}
count = count + i;
printf("%d", count);
return 0;
}
Which of one of the following options is correct?- (a)The program will compile successfully and output 10 when executed.
- (b)The program will not compile successfully.
- (b)The program will compile successfully and output 8 when executed.
- (d)The program will compile successfully and output 13 when executed.
Consider the following C program. Assume
parameters to a function are evaluated from right to
left.
#include <stdio.h>
int g(int p) {
printf("%d", p);
return p;
}
int h(int q) {
printf("%d", q);
return q;
}
void f(int x, int y) {
g(x);
h(y);
}
int main() {
f(g(10), h(20));
}
Which one of the following options is the CORRECT
output of the above C program?- (a)20101020
- (b)10202010
- (c)20102010
- (d)10201020
Self-check
Question 1
Which of these are undefined behaviour? (a) a[i] = i++; (b) y = (x++, x); (c) i++ && i++; (d) f(i, i++); (e) x = x + 1; (f) i = ++i + 1;
Show answerHide
(a), (d) and (f) are undefined. In (a) i is read to pick the element and modified by i++ with no sequencing. In (d) i is read and modified between the argument evaluations, which are unsequenced (the comma between arguments is not the comma operator, so it gives no ordering). In (f) i is modified by ++i and again by the assignment.
(b) is fine because the comma operator sequences the two parts. (c) is fine because && has a sequence point after its left operand. (e) is fine: x is read only to compute the value stored.
Question 2
What does this print?
#include <stdio.h>
int main(void) {
int a = 3;
int r = (a > 2) ? a++ : a--;
printf("%d %d\n", r, a);
return 0;
}Show answerHide
The condition is true, so only a++ is evaluated: its value 3 is stored in r and a becomes 4. The branch a-- never runs.
3 4Question 3
What does this print, and why is it not undefined even though i is modified twice?
#include <stdio.h>
int main(void) {
int i = 1;
i = (i++, i + 5);
printf("%d\n", i);
return 0;
}Show answerHide
7The comma operator is a sequence point. So i++ completes (making i equal to 2) before i + 5 is evaluated (7). Only then is 7 stored in i. The two modifications are separated by a sequence point, so the rule is not broken.
Question 4
What kind of behaviour is (signed char)200, and what does gcc give?
Show answerHide
It is implementation-defined (converting an out-of-range value to a signed type). gcc keeps the low 8 bits, giving , as the animation on the types and operators page shows.
Revision card
One-screen revision
- Undefined: anything may happen; no answer exists. Unspecified: one of several allowed, may vary. Implementation-defined: compiler documents it.
- Sequence points:
;, after the first operand of&&||?:,, before a function call. - Never modify a variable twice, or modify and read it, between two sequence points.
- Precedence decides parsing, not the order of evaluation.
- Signed overflow,
/ 0, out of bounds, null or dangling dereference, writing to literals: undefined. - If the code breaks the rule and an option says "undefined", that matches the C standard.
References
- ·ISO/IEC 9899:2011 (C11), Section 3.4 Behavior (3.4.1 implementation-defined, 3.4.3 undefined, 3.4.4 unspecified), Section 6.5 paragraphs 1 to 3, Annex C Sequence points
- ·Kernighan and Ritchie, The C Programming Language, 2nd ed., Section 2.12 Precedence and Order of Evaluation
Verification
The five side-by-side runs in the animation area are real: each program was compiled and run at -O0 to -O3 and under UBSan or ASan by gcc, and the output, signal and diagnostic are recorded as printed. All other C programs on this page are compiled with -Wall -Wextra -Werror and run by npm run verify:c. The definitions are paraphrased from C11 section 3.4; the prose, the classification table and the GATE-pattern claims have NOT yet had a human textbook check (the independent second pass is described at the end of these notes). One GATE convention is flagged needs verification below. SECOND PASS (2026-11-02, done by the assistant, not a human check): every Self-check question was solved before the printed answer was read and all agreed; the definitions, worked examples, cheat box and traps were read claim by claim and the numbers re-derived by hand (long C programs were not re-read line by line: they are compiled and compared by npm run verify:c, and their printed outputs were checked against the prose). No defect found. Status raised to self-checked; verified needs a textbook or human check.
last verified: never
My notes (saved in this browser)