C Programming

Module 1· 12 min read· 10 Questions·completed

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:

Diagram / Text
                                  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)
  1. 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 element a[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, expressions a + b). It cannot appear on the left side of an assignment.
  2. 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.
  3. 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()), either g() or h() may run first).
  4. Implementation-Defined Behavior: Unspecified behavior where the compiler author is required to document the choice (e.g., whether char is signed or unsigned by default, size of int, behavior of right-shifting negative integers).
  5. 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").
  6. 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:

Range for n-bit signed integer: [2n1,2n11]\text{Range for } n\text{-bit signed integer: } [-2^{n-1}, 2^{n-1} - 1] Range for n-bit unsigned integer: [0,2n1]\text{Range for } n\text{-bit unsigned integer: } [0, 2^n - 1]

For an 8-bit integer (char):

  • Signed: [128,+127][-128, +127]
  • Unsigned: [0,255][0, 255]

[!CRITICAL] GATE Trap: Signed vs. Unsigned Overflow!

  • Unsigned integer overflow is WELL-DEFINED: It wraps around modulo 2n2^n (e.g., in 8-bit unsigned: 255+1=0255 + 1 = 0).
  • 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:

  1. Any integer type smaller than int (such as char, signed char, unsigned char, short, unsigned short, and bitfields) is automatically promoted to int if int can represent all values of the original type; otherwise, it is promoted to unsigned int.
  2. Once promoted, if operands still differ in type, the Usual Arithmetic Conversions hierarchy applies: long double>double>float>unsigned long long>long long>unsigned long>long>unsigned int>int\text{long double} > \text{double} > \text{float} > \text{unsigned long long} > \text{long long} > \text{unsigned long} > \text{long} > \text{unsigned int} > \text{int}
C
// 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!
C
#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;
}
Warning

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 2321=4,294,967,2952^{32} - 1 = 4,294,967,295. Thus, the comparison becomes 4,294,967,295<44,294,967,295 < 4, 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.

LevelCategoryOperatorsAssociativityKey Trap / Notes
1 (Highest)Postfix() [] -> . ++ -- (postfix)Left to RightPostfix increment has higher precedence than unary dereference *
2Unary (Prefix)+ - ! ~ ++ -- (prefix) * & sizeof (type)Right to LeftGrouped right-to-left: *p++ parses as *(p++)
3Multiplicative* / %Left to RightDivision truncation toward zero in C99/C11
4Additive+ -Left to RightPointer arithmetic scales by sizeof(*ptr)
5Shift<< >>Left to RightPrecedence is LOWER than additive (a + b << 2 is (a + b) << 2)
6Relational< <= > >=Left to Right1 < x < 5 evaluates as (1 < x) < 5 (evaluates to 1 or 0 < 5, always 1!)
7Equality== !=Left to RightLower precedence than relational operators
8Bitwise AND&Left to RightLower precedence than equality: if (x & 1 == 0) parses as x & (1 == 0)!
9Bitwise XOR^Left to Right
10Bitwise OR``Left to Right
11Logical AND&&Left to RightShort-circuit evaluation; introduces sequence point
12Logical OR``
13Conditional? :Right to LeftRight-associative: a ? b : c ? d : e parses as a ? b : (c ? d : e)
14Assignment= += -= *= /= etc.Right to Lefta = b = c = 10 assigns c=10, then b=10, then a=10
15 (Lowest)Comma,Left to RightDiscards 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.

Diagram / Text
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#

C
#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:

  1. An object's stored value shall be modified at most once by the evaluation of an expression.
  2. The prior value shall be accessed only to determine the value to be stored.

Violating either condition results in UNDEFINED BEHAVIOR (UB)!

C
// 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:

  1. End of a full expression: The semicolon (;) ending a statement.
  2. The Logical AND operator (&&): Evaluates left operand. If false, stops immediately. Sequence point occurs after left operand.
  3. The Logical OR operator (||): Evaluates left operand. If true, stops immediately. Sequence point occurs after left operand.
  4. The Ternary Conditional operator (? :): Evaluates first operand before ?. Sequence point occurs after condition.
  5. The Comma operator (,): Evaluates left expression, performs all side effects, then evaluates right expression.
  6. Function Call: Evaluates all argument expressions, then a sequence point occurs before entering the function body.
C
// 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 / FormulaExpression in CExplanation / Why it works
Check if Odd(n & 1) != 0Tests the least significant bit (LSB)
Check Power of 2n > 0 && (n & (n - 1)) == 0nn has exactly one bit set; n1n-1 flips that bit and all bits below
Clear Lowest Set Bitn = n & (n - 1)Clears the rightmost set 1-bit (Kernighan's bit count algorithm)
Extract Lowest Set Bitn & (-n)In 2's complement, n=n+1-n = \sim n + 1, isolating the lowest 1-bit
Set kk-th bit`n(1U << k)`
Clear kk-th bitn & ~(1U << k)Inverts mask to have 0 at kk, then ANDs
Toggle kk-th bitn ^ (1U << k)XOR with 1 flips the bit
Multiply by 2k2^kn << kLeft shift moves bits to higher powers of 2
Divide by 2k2^kn >> kRight shift moves bits down (floor division for positive numbers)
Warning

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#

  1. Explicit Parentheses for Bitwise Operators: Always write if ((x & MASK) == 0) instead of if (x & MASK == 0). Precedence of == is higher than &!
  2. Never Mutate a Variable Multiple Times in One Statement: Avoid code like i = ++i + 2. Keep assignments atomic and sequential.
  3. Use Unsigned Constants for Bitwise Shifts: Write 1U << 31 rather than 1 << 31. Shifting into the sign bit of a signed integer causes signed overflow (UB in C99/C11).
  4. 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.