Recommended resources
The freely available resources this lesson will direct you to.
- C_18 Operators in C - Part 6 | Bitwise Operators | C Programming Tutorialswww.youtube.com
- GATE 2023 CSE Exam | Operators and Expressions I | C Programming for Gate CSE | BYJU'S GATEwww.youtube.com
- C Operator Precedenceen.cppreference.com
- Implicit conversions - cppreference.comicpc.student.cs.uwaterloo.ca
Hello! This is the first lesson in a module on precise C program tracing. Before tracing loops, recursion, or pointers, you need a reliable way to evaluate a single expression. A wrong assumption about integer division, an unsigned operand, or a skipped increment can change every value that follows.
In this lesson, you will determine an expression’s value and type, work with bitwise operators, apply C’s precedence rules, and identify when short-circuit evaluation skips work. The aim is not to guess what a compiler will print, but to justify each result—or recognize when C does not guarantee one.
Types determine what arithmetic means
Consider these declarations:
int a = 5, b = 2;
double x = a / b;
double y = (double)a / b;
The assignment to x does not make a / b floating-point division. Both operands of that division are int, so its result is the int value 2; only then is 2 converted to double for storage in x. For y, the cast converts a before division, so the division produces 2.5.
A useful tracing habit is to write down the type of each intermediate result, not just the type of the variable receiving it.
GATE 2023 CSE Exam | Operators and Expressions I | C Programming for Gate CSE | BYJU'S GATE
Watch “Operators and Expressions I” from BYJU'S Exam Prep GATE & ESE for a short demonstration of integer division, mixed-type division, and casting.
Watch integer division for the quotient-and-remainder example, then mixed-type division. Finish with casting before division. Focus on when conversion occurs; the destination variable cannot retroactively change a division already performed.
For integers, / discards the fractional part toward zero. The % operator gives the corresponding remainder and requires integer operands. Thus -5 / 2 is -2, while -5 % 2 is -1. Division or remainder by zero has undefined behavior; it is not safe to predict a particular crash or message.
Promotions and a common arithmetic type
C often converts operands before applying an operator. First, small integer types such as char and short undergo integer promotion: they become int if int can represent every value of the original type, or unsigned int otherwise. Promotion preserves their value.
Implicit conversions - cppreference.com
Read cppreference’s “Implicit conversions” for the rules behind small-integer promotion and conversion to true or false.
Under “Implicit conversion semantics,” find “Integer promotions.” Read the promotion rule, focusing on the test of whether int can hold the original type’s entire range. Then find “Boolean conversion” and read the zero rule. We will apply the arithmetic and assignment conversions in the examples below.
For arithmetic involving different types, the usual arithmetic conversions establish a common type for the operation. The cases most useful in expression tracing are:
- If one operand is
doubleand the other isint, theintis converted todouble. An unsuffixed literal such as2.0has typedouble;2.0fhas typefloat. - With two integer operands, promote small integer types first. If the promoted types differ, signedness and rank determine a common integer type.
- If
intmeetsunsigned int, both are operated on asunsigned int. A negativeintconverted to an unsigned type wraps modulo one more than that unsigned type’s maximum value.
For example, -1 < 1u is false on a system where 1u is unsigned int. The comparison converts -1 to unsigned int; it does not compare negative one with positive one as mathematical integers. Signed/unsigned mixtures of different ranks need more care: a higher-rank signed type is used if it can represent all values of the unsigned type; otherwise an appropriate unsigned type is used. Do not infer this solely from whether the machine is described as “32-bit” or “64-bit”—the relevant type ranges matter.

Assignment is a separate conversion. After the right-hand expression has been evaluated, its result is converted to the destination type. int n = 3.9; stores 3, provided the floating-point value is representable as an int. Conversely, double d = 5 / 2; stores 2.0, because the integer division has already happened.
A final boundary matters in exam questions: signed integer arithmetic that overflows has undefined behavior. Unsigned arithmetic, by contrast, wraps modulo the range of its unsigned type. Treating both as ordinary wraparound is a tracing error.
Bitwise operations act on integer bits
Logical and bitwise operators may look similar, but they answer different questions. && asks whether both operands are nonzero and produces 0 or 1. & combines corresponding bits and can produce another integer value.
C_18 Operators in C - Part 6 | Bitwise Operators | C Programming Tutorials
Watch “Bitwise Operators” from Jenny's Lectures CS IT for a visual calculation of AND, OR, and XOR on binary representations.
Start with AND versus logical AND, then follow the binary calculation of 10 & 5. Watch bitwise OR and XOR. Focus on matching bit positions; the video’s examples use positive integers, but negative integer operands are also legal in C.
Write 10 and 6 with corresponding positions aligned:
1010 (10)
0110 ( 6)
----
0010 (10 & 6 = 2)
1110 (10 | 6 = 14)
1100 (10 ^ 6 = 12)
At each position, & sets a bit only if both input bits are 1; | sets it if either is 1; ^ sets it if the input bits differ. Unlike && and ||, the bitwise binary operators do not short-circuit their operands.
Two further operators work at the bit level:
~complements the bits of its promoted integer operand. Width matters:~0u, for example, has every bit set in anunsigned int, not merely the four bits you might draw on paper.<<and>>shift bits. For a nonnegative value where the result remains valid,5u << 1gives10, and10u >> 1gives5. A negative shift count, or a count at least as large as the width of the promoted left operand, has undefined behavior. Avoid assuming a portable result for right-shifting a negative signed value.
For small, nonnegative bit patterns, drawing a few bits is efficient. Before applying that picture to ~ or a shift, check the operand’s promoted type and width.
Precedence tells you how an expression is grouped
Precedence decides which operands belong to which operators; associativity resolves grouping among operators at the same precedence. Neither rule, by itself, promises that subexpressions execute in the order you read them.
The following order covers the operators most likely to interact in this lesson, from tighter to looser binding:
| Group | Operators |
|---|---|
| Postfix, then unary | x++; !, ~, casts, ++x |
| Multiplicative, then additive | * / %; + - |
| Shifts | << >> |
| Relational, then equality | < <= > >=; == != |
| Bitwise | &, then ^, then ` |
| Logical | &&, then ` |
| Conditional, assignment, comma | ?:; = and compound assignments; , |
Operators in a table cell need not share a precedence: “then” marks a move to the next, lower level. Consult the full table when an unfamiliar operator appears.
Read cppreference’s “C Operator Precedence” as a parsing reference, rather than trying to memorize every row at once.
At the top of the page, read the precedence table from row 1 (postfix operators) through row 15 (comma); the complete table shows where shifts, comparisons, bitwise operators, and logical operators fall relative to one another. Below it, read the parsing examples and Notes. Pay particular attention to the note distinguishing precedence and associativity from order of evaluation.
Now parenthesize before calculating. 1 << 2 + 1 means 1 << (2 + 1), so its value is 8, not 5. Equality binds more tightly than bitwise AND: 2 & 3 == 2 means 2 & (3 == 2), yielding 0. In contrast, (2 & 3) == 2 yields 1.
Associativity matters for equal-precedence operators. 8 / 4 / 2 groups as (8 / 4) / 2, giving 1; a = b = 5 groups as a = (b = 5), assigning 5 to each variable. Explicit parentheses are the safest way to show your intended grouping when writing C.
Short-circuiting determines what actually runs
C treats zero as false and nonzero as true for conditions. The logical operators !, &&, and || produce an int result of either 0 or 1; they do not return an operand’s original nonzero value.
Crucially, && and || evaluate their left operand first:
- For
left && right, a zeroleftmakes the result0;rightis not evaluated. - For
left || right, a nonzeroleftmakes the result1;rightis not evaluated.
That is why this condition is safe when denominator is zero:
denominator != 0 && 12 / denominator > 2
The division is skipped if the left condition is false. Replacing && with & would both change the grouping considerations and lose short-circuit protection; the division could then cause undefined behavior.
Skipping an operand also skips its side effects. Starting with int x = 4;, evaluating 0 && ++x yields 0 and leaves x at 4. Evaluating 1 && ++x evaluates the increment, leaves x at 5, and yields 1. The && operator sequences its left operand before its right operand if the right operand is needed.
The conditional operator also evaluates only one selected branch: condition ? first : second evaluates condition, then either first or second. This is different from assuming that high precedence means early execution. Precedence describes grouping. Without a sequencing rule, side-effect-heavy expressions can lack a predictable result. For instance, i++ + i has undefined behavior because a modification and another access to i are unsequenced. Do not “solve” such an expression by choosing an arbitrary left-to-right order.
For a reliable trace, use this sequence of checks:
- Group the expression using precedence, associativity, and parentheses.
- Determine types and any promotions or conversions at each operation.
- Follow sequencing rules, marking branches and operands skipped by short-circuiting.
- Calculate, but stop if the expression has undefined behavior rather than inventing an output.
Key takeaways
An expression’s destination type does not determine how its earlier operations were performed. Small integers can be promoted; mixed operands can acquire a common arithmetic type; and signed/unsigned comparisons deserve particular scrutiny. Bitwise operators calculate integer bit patterns, whereas logical operators produce 0 or 1 and may skip evaluation. Finally, parsing order is not execution order—check both before tracing a side effect.
The next lesson builds on these rules to trace conditionals, loops, and function calls across multiple statements. There, a single expression’s result becomes the starting state for the rest of the program.