1. Types, Operators & Precedence Traps
Integer promotion, 2's complement, signed/unsigned comparisons, sequence points, evaluation order, operator precedence hierarchy, and bitwise manipulation in C
GATE CS & UGC NET JRF Core Subject Exam Weightage: 2–3 Direct Questions on operator precedence/associativity, tricky increment/decrement side-effects, signed vs unsigned implicit promotions, and sequence point evaluation.
1. Technical Jargon & ISO C Standards Taxonomy#
In competitive exams like GATE and UGC NET, precise technical terminology is essential. ISO C categorizes behavior into four distinct tiers:
C Execution Behaviors
│
┌───────────────────┬───────────────┴───────────────┬───────────────────┐
▼ ▼ ▼ ▼
Well-Defined Implementation-Defined Unspecified Undefined (UB)
Guaranteed by C Compiler must document Compiler chooses ANYTHING can happen
Standard across (e.g., sizeof(int), valid options (Crash, corrupt data,
all platforms sign of integer div) (e.g., arg order) wrong output, pass)
- L-Value vs. R-Value:
- L-Value (Locator Value): An expression that designates an addressable memory location (e.g., variable identifier
x, dereferenced pointer*p, array elementa[i]). An l-value can appear on the left side of an assignment operator (=). - R-Value (Read Value): A transient data value without a persistent addressable storage location (e.g., literals
42, expressionsa + b). It cannot appear on the left side of an assignment.
- L-Value (Locator Value): An expression that designates an addressable memory location (e.g., variable identifier
- Undefined Behavior (UB): Behavior upon use of a nonportable or erroneous program construct (e.g., signed integer overflow, division by zero, reading uninitialized memory, modifying a variable twice between sequence points like
i = i++). The compiler is free to do anything. - Unspecified Behavior: Behavior where the standard provides two or more possibilities and imposes no requirement on which is chosen in any instance (e.g., order of evaluation of function arguments: in
f(g(), h()), eitherg()orh()may run first). - Implementation-Defined Behavior: Unspecified behavior where the compiler author is required to document the choice (e.g., whether
charis signed or unsigned by default, size ofint, behavior of right-shifting negative integers). - Sequence Point: A point in the program's execution sequence at which all previous side-effects are guaranteed to have completed and no subsequent side-effects have yet taken place (pre-C11 terminology; C11 uses the relation "sequenced before").
- Side Effect: A change in the state of the execution environment (e.g., modifying an object in memory, accessing a volatile object, modifying a file).
2. Integer Representation, Limits & Promotion Rules#
2.1 Two's Complement Range & Overflow#
Most modern systems represent signed integers using Two's Complement:
For an 8-bit integer (char):
- Signed:
- Unsigned:
[!CRITICAL] GATE Trap: Signed vs. Unsigned Overflow!
- Unsigned integer overflow is WELL-DEFINED: It wraps around modulo (e.g., in 8-bit unsigned: ).
- Signed integer overflow is UNDEFINED BEHAVIOR (UB): Compilers can optimize away loops assuming signed integers never overflow!
2.2 Integer Promotion Rules (The Usual Arithmetic Conversions)#
Before executing any binary arithmetic or bitwise operator, C applies Integer Promotion:
- Any integer type smaller than
int(such aschar,signed char,unsigned char,short,unsigned short, and bitfields) is automatically promoted tointifintcan represent all values of the original type; otherwise, it is promoted tounsigned int. - Once promoted, if operands still differ in type, the Usual Arithmetic Conversions hierarchy applies:
// Tricky GATE Example:
char a = 100;
char b = 50;
char c = a * b / b; // a * b promotes to int (5000), does NOT overflow char!
// Result: 100
2.3 The Classic Signed vs. Unsigned Comparison Trap#
When a signed integer and an unsigned integer of the same rank are compared:
- The signed operand is implicitly converted to unsigned!
- Negative numbers become huge positive numbers!
#include <stdio.h>
int main(void) {
int a = -1;
unsigned int b = 1;
// sizeof operator returns type size_t (which is an unsigned integer type!)
if (a < sizeof(int)) {
printf("True\n");
} else {
printf("False\n"); // PRINTS "False"!
}
if (a < b) {
printf("Less\n");
} else {
printf("Greater or Equal\n"); // PRINTS "Greater or Equal"!
}
return 0;
}
Why does -1 < sizeof(int) evaluate to FALSE?
sizeof(int) has type size_t (unsigned integer, typically 32-bit or 64-bit).
When evaluating -1 < sizeof(int), -1 is converted to unsigned int.
In two's complement, -1 converted to unsigned becomes .
Thus, the comparison becomes , which is FALSE!
3. Operator Precedence & Associativity Hierarchy#
C features 15 precedence levels. Memorizing the relative priorities of unary, arithmetic, shift, relational, equality, bitwise, logical, conditional, and assignment operators is tested every year in GATE.
| Level | Category | Operators | Associativity | Key Trap / Notes |
|---|---|---|---|---|
| 1 (Highest) | Postfix | () [] -> . ++ -- (postfix) | Left to Right | Postfix increment has higher precedence than unary dereference * |
| 2 | Unary (Prefix) | + - ! ~ ++ -- (prefix) * & sizeof (type) | Right to Left | Grouped right-to-left: *p++ parses as *(p++) |
| 3 | Multiplicative | * / % | Left to Right | Division truncation toward zero in C99/C11 |
| 4 | Additive | + - | Left to Right | Pointer arithmetic scales by sizeof(*ptr) |
| 5 | Shift | << >> | Left to Right | Precedence is LOWER than additive (a + b << 2 is (a + b) << 2) |
| 6 | Relational | < <= > >= | Left to Right | 1 < x < 5 evaluates as (1 < x) < 5 (evaluates to 1 or 0 < 5, always 1!) |
| 7 | Equality | == != | Left to Right | Lower precedence than relational operators |
| 8 | Bitwise AND | & | Left to Right | Lower precedence than equality: if (x & 1 == 0) parses as x & (1 == 0)! |
| 9 | Bitwise XOR | ^ | Left to Right | |
| 10 | Bitwise OR | ` | ` | Left to Right |
| 11 | Logical AND | && | Left to Right | Short-circuit evaluation; introduces sequence point |
| 12 | Logical OR | ` | ` | |
| 13 | Conditional | ? : | Right to Left | Right-associative: a ? b : c ? d : e parses as a ? b : (c ? d : e) |
| 14 | Assignment | = += -= *= /= etc. | Right to Left | a = b = c = 10 assigns c=10, then b=10, then a=10 |
| 15 (Lowest) | Comma | , | Left to Right | Discards left operand, returns right operand; introduces sequence point |
4. Syntax Awareness: Pointer Dereference & Increment Traps#
The interaction between unary * (dereference) and ++ / -- is the single most tested syntax construct in C.
Four Combinations with Pointer p:
Expression | Precedence Parse | Meaning / Effect
------------+------------------+-------------------------------------------------------------
*p++ | *(p++) | Yields *p (current value), THEN increments pointer p itself
(*p)++ | (*p)++ | Yields *p, THEN increments the integer value stored at *p
*++p | *(++p) | Increments pointer p first, THEN dereferences new address
++*p | ++(*p) | Increments integer value stored at *p, yields incremented value
Detailed Code Walkthrough#
#include <stdio.h>
int main(void) {
int arr[] = {10, 20, 30, 40, 50};
int *p = arr;
printf("%d\n", *p++); // Prints 10. p now points to arr[1] (address of 20).
printf("%d\n", (*p)++); // Prints 20. arr[1] becomes 21. p still points to arr[1].
printf("%d\n", *++p); // p moves to arr[2] (30), then dereferences. Prints 30.
printf("%d\n", ++*p); // arr[2] (30) is incremented to 31, then prints 31.
// Final array state: {10, 21, 31, 40, 50}
return 0;
}
5. Sequence Points & Undefined Evaluation Order#
5.1 The Rule of Sequence Points#
Between the previous and next sequence point:
- An object's stored value shall be modified at most once by the evaluation of an expression.
- The prior value shall be accessed only to determine the value to be stored.
Violating either condition results in UNDEFINED BEHAVIOR (UB)!
// ILLEGAL: Undefined Behavior Examples
i = i++; // UB: modified twice without sequence point
i = ++i; // UB: modified twice without sequence point
a[i] = i++; // UB: i accessed to determine address AND modified without sequence point
func(i++, i++); // UB: order of argument evaluation is unspecified, modified without seq point
printf("%d %d", ++i, i++); // UB: modifies and reads i without sequence point
5.2 Guaranteed Sequence Points in C#
A sequence point occurs at:
- End of a full expression: The semicolon (
;) ending a statement. - The Logical AND operator (
&&): Evaluates left operand. If false, stops immediately. Sequence point occurs after left operand. - The Logical OR operator (
||): Evaluates left operand. If true, stops immediately. Sequence point occurs after left operand. - The Ternary Conditional operator (
? :): Evaluates first operand before?. Sequence point occurs after condition. - The Comma operator (
,): Evaluates left expression, performs all side effects, then evaluates right expression. - Function Call: Evaluates all argument expressions, then a sequence point occurs before entering the function body.
// Tricky GATE Question on Short-Circuiting:
#include <stdio.h>
int main(void) {
int a = 0, b = 1, c = 2;
// In a && (++b), since a is 0 (false), (++b) is NEVER EVALUATED!
// b remains 1.
int res = a && (++b);
printf("res=%d, b=%d\n", res, b); // res=0, b=1
// In (b || ++c), since b is 1 (true), (++c) is NEVER EVALUATED!
// c remains 2.
int res2 = b || (++c);
printf("res2=%d, c=%d\n", res2, c); // res2=1, c=2
return 0;
}
6. Bitwise Manipulation Formulas & Idioms#
Bitwise operations operate directly on binary representations and are heavily tested in GATE CS:
| Idiom / Formula | Expression in C | Explanation / Why it works |
|---|---|---|
| Check if Odd | (n & 1) != 0 | Tests the least significant bit (LSB) |
| Check Power of 2 | n > 0 && (n & (n - 1)) == 0 | has exactly one bit set; flips that bit and all bits below |
| Clear Lowest Set Bit | n = n & (n - 1) | Clears the rightmost set 1-bit (Kernighan's bit count algorithm) |
| Extract Lowest Set Bit | n & (-n) | In 2's complement, , isolating the lowest 1-bit |
| Set -th bit | `n | (1U << k)` |
| Clear -th bit | n & ~(1U << k) | Inverts mask to have 0 at , then ANDs |
| Toggle -th bit | n ^ (1U << k) | XOR with 1 flips the bit |
| Multiply by | n << k | Left shift moves bits to higher powers of 2 |
| Divide by | n >> k | Right shift moves bits down (floor division for positive numbers) |
Shift Count Constraints in ISO C:
If the shift count is negative or greater than or equal to the width of the promoted left operand, the behavior is UNDEFINED!
Example: For a 32-bit int, 1 << 32 and 1 << -1 are Undefined Behavior.
7. Best Practices & Defensive Coding in C#
- Explicit Parentheses for Bitwise Operators:
Always write
if ((x & MASK) == 0)instead ofif (x & MASK == 0). Precedence of==is higher than&! - Never Mutate a Variable Multiple Times in One Statement:
Avoid code like
i = ++i + 2. Keep assignments atomic and sequential. - Use Unsigned Constants for Bitwise Shifts:
Write
1U << 31rather than1 << 31. Shifting into the sign bit of a signed integer causes signed overflow (UB in C99/C11). - Guard Comparisons against Signed-Unsigned Pitfalls: Cast or explicitly ensure both sides share matching signedness before comparing.
Practice Quiz
Test your understanding with step-by-step solutions
Practice Quiz
10 questions · 90s per question
Each question has a 90-second time limit. Unanswered questions will be auto-submitted when time runs out.