|$ curl https://forge-ai.dev/api/markdown?path=docs/c/operators
$cat docs/c-—-operators-&-expressions.md
updated Recently·35 min read·published

C — Operators & Expressions

COperatorsExpressionsBeginnerBeginner🎯Free Tools
Introduction

Operators are symbols that perform operations on values and variables. C provides a rich set of operators spanning arithmetic, comparison, logical, bitwise, assignment, and special-purpose categories. Mastering operators is essential for writing correct, efficient, and idiomatic C code.

C operators can be classified by arity — unary (one operand), binary (two operands), and ternary (three operands). Each operator has a precedence level that determines evaluation order, and many have associativity rules (left-to-right or right-to-left). Understanding precedence prevents subtle bugs, and using parentheses liberally improves readability.

This page covers every major operator category, the complete precedence table, type casting, integer promotion rules, and the concept of sequence points and undefined behavior.

intro.c
C
1#include <stdio.h>
2
3int main(void) {
4 int a = 10, b = 3;
5
6 // Arithmetic
7 printf("%d + %d = %d\n", a, b, a + b);
8 printf("%d %% %d = %d\n", a, b, a % b);
9
10 // Relational
11 printf("%d > %d is %d\n", a, b, a > b);
12
13 // Logical
14 printf("%d && %d = %d\n", a, b, a && b);
15
16 // Ternary
17 int max = (a > b) ? a : b;
18 printf("Max: %d\n", max);
19
20 return 0;
21}
Arithmetic Operators

C provides five arithmetic operators. Division truncates toward zero for integers, and the modulo operator % returns the remainder with the sign of the dividend.

OperatorNameExampleResult
+Addition10 + 313
-Subtraction10 - 37
*Multiplication10 * 330
/Division10 / 33 (truncated)
%Modulo (remainder)10 % 31
++Incrementx++ / ++xx + 1
--Decrementx-- / --xx - 1
arithmetic.c
C
1#include <stdio.h>
2
3int main(void) {
4 // Integer division truncates toward zero
5 printf("10 / 3 = %d\n", 10 / 3); // 3
6 printf("-10 / 3 = %d\n", -10 / 3); // -3
7
8 // Modulo: sign follows the dividend
9 printf("10 %% 3 = %d\n", 10 % 3); // 1
10 printf("-10 %% 3 = %d\n", -10 % 3); // -1
11 printf("10 %% -3 = %d\n", 10 % -3); // 1
12
13 // Float division — use floating-point operands
14 printf("10.0 / 3 = %f\n", 10.0 / 3); // 3.333333
15
16 // Prefix vs postfix increment
17 int x = 5;
18 int a = x++; // a = 5, x = 6 (post: returns old value)
19 int b = ++x; // b = 7, x = 7 (pre: returns new value)
20 printf("a=%d, b=%d, x=%d\n", a, b, x);
21
22 // Chained operations
23 int result = 2 + 3 * 4; // 14, not 20 (* has higher precedence)
24 int grouped = (2 + 3) * 4; // 20
25 printf("result=%d, grouped=%d\n", result, grouped);
26
27 return 0;
28}

warning

Division by zero is undefined behavior for both integer and floating-point division. Always check the divisor before dividing. Integer overflow on signed types is also undefined behavior — unsigned overflow wraps around predictably.
Relational Operators

Relational operators compare two values and return 0 (false) or 1 (true). They have lower precedence than arithmetic operators.

OperatorMeaningExampleResult
==Equal to5 == 51
!=Not equal to5 != 31
<Less than3 < 51
>Greater than5 > 31
<=Less than or equal5 <= 51
>=Greater than or equal5 >= 31
relational.c
C
1#include <stdio.h>
2
3int main(void) {
4 int x = 5, y = 10;
5
6 // Relational operators return 0 or 1
7 printf("%d == %d => %d\n", x, y, x == y); // 0
8 printf("%d != %d => %d\n", x, y, x != y); // 1
9 printf("%d < %d => %d\n", x, y, x < y); // 1
10 printf("%d > %d => %d\n", x, y, x > y); // 0
11 printf("%d <= %d => %d\n", x, y, x <= y); // 1
12 printf("%d >= %d => %d\n", x, y, x >= y); // 0
13
14 // Chained comparisons DON'T work in C (unlike Python)
15 // if (1 < x < 10) is WRONG — evaluates as (1 < x) < 10
16 // Always use: if (x > 1 && x < 10)
17
18 // Safe range check
19 int val = 5;
20 if (val > 0 && val <= 100) {
21 printf("Value %d is in range [1, 100]\n", val);
22 }
23
24 return 0;
25}
Logical Operators

Logical operators combine boolean expressions. C uses short-circuit evaluation— the right operand is only evaluated if the left operand doesn't determine the result.

OperatorMeaningShort-circuit?
!Logical NOTNo (unary)
&&Logical ANDYes — if left is 0, right is not evaluated
||Logical ORYes — if left is non-zero, right is not evaluated
logical.c
C
1#include <stdio.h>
2
3int main(void) {
4 int a = 1, b = 0;
5
6 // Logical NOT
7 printf("!%d = %d\n", a, !a); // 0
8 printf("!%d = %d\n", b, !b); // 1
9
10 // Logical AND — both must be non-zero
11 printf("%d && %d = %d\n", a, b, a && b); // 0
12 printf("%d && %d = %d\n", a, a, a && a); // 1
13
14 // Logical OR — at least one must be non-zero
15 printf("%d || %d = %d\n", a, b, a || b); // 1
16 printf("%d || %d = %d\n", b, b, b || b); // 0
17
18 // Short-circuit evaluation — critical pattern
19 int x = 0;
20 // The second operand is NEVER evaluated if x is 0
21 if (x != 0 && 10 / x > 2) {
22 printf("Safe division\n");
23 }
24
25 // Without short-circuit, 10/0 would crash
26 // This pattern is essential for NULL pointer checks
27 int *ptr = (void *)0; // NULL
28 if (ptr != ((void *)0) && *ptr == 42) {
29 printf("Found it\n");
30 } else {
31 printf("ptr is NULL, safely skipped dereference\n");
32 }
33
34 // Non-short-circuit equivalent (avoids & and | operators)
35 // & and | are BITWISE, not logical — different precedence!
36
37 return 0;
38}
🔥

pro tip

The bitwise operators & and | have LOWER precedence than comparison operators. Writing if (a & MASK == 0) is parsed as if (a & (MASK == 0)) — a common and subtle bug. Always use parentheses: if ((a & MASK) == 0).
Bitwise Operators

Bitwise operators manipulate individual bits within integers. They are essential for hardware programming, flags, bitmasks, and efficient computation.

OperatorNameExampleResult (binary)
&Bitwise AND0b1100 & 0b10100b1000
|Bitwise OR0b1100 | 0b10100b1110
^Bitwise XOR0b1100 ^ 0b10100b0110
~Bitwise NOT~0b000011110b11110000
<<Left shift1 << 38
>>Right shift16 >> 24
bitwise.c
C
1#include <stdio.h>
2
3int main(void) {
4 unsigned int flags = 0;
5
6 // Set bits using OR
7 flags |= (1u << 0); // Set bit 0
8 flags |= (1u << 3); // Set bit 3
9 printf("Flags: %u\n", flags); // 9 (binary: 1001)
10
11 // Check bits using AND
12 if (flags & (1u << 0)) {
13 printf("Bit 0 is set\n");
14 }
15
16 // Clear bits using AND with complement
17 flags &= ~(1u << 0); // Clear bit 0
18 printf("After clearing bit 0: %u\n", flags); // 8
19
20 // Toggle bits using XOR
21 flags ^= (1u << 3); // Toggle bit 3
22 printf("After toggling bit 3: %u\n", flags); // 0
23
24 // Left shift = multiply by 2
25 int val = 5;
26 printf("%d << 1 = %d\n", val, val << 1); // 10
27 printf("%d << 3 = %d\n", val, val << 3); // 40
28
29 // Right shift = divide by 2
30 printf("%d >> 1 = %d\n", val, val >> 1); // 2
31
32 // Bitwise NOT — inverts all bits
33 unsigned char byte = 0x0F;
34 printf("~0x%02X = 0x%02X\n", byte, (unsigned char)~byte);
35
36 return 0;
37}
Assignment Operators

Assignment operators store values in variables. C provides compound assignment operators that combine an arithmetic/bitwise operation with assignment.

OperatorEquivalentExample
=Simple assignmentx = 5
+=x = x + yx += 3
-=x = x - yx -= 2
*=x = x * yx *= 4
/=x = x / yx /= 2
%=x = x % yx %= 3
&=x = x & yx &= 0xFF
|=x = x | yx |= mask
^=x = x ^ yx ^= toggle
<<=x = x << yx <<= 2
>>=x = x >> yx >>= 1
assignment.c
C
1#include <stdio.h>
2
3int main(void) {
4 int x = 10;
5
6 x += 5; printf("x += 5 => %d\n", x); // 15
7 x -= 3; printf("x -= 3 => %d\n", x); // 12
8 x *= 2; printf("x *= 2 => %d\n", x); // 24
9 x /= 4; printf("x /= 4 => %d\n", x); // 6
10 x %= 4; printf("x %%= 4 => %d\n", x); // 2
11
12 // Assignment is an expression — it returns the assigned value
13 int a, b;
14 a = b = 10; // right-to-left: b=10, then a=b (a=10)
15 printf("a=%d, b=%d\n", a, b);
16
17 // Compound assignment evaluates the operand only once
18 // arr[i++] += 5 is safe; arr[i++] = arr[i++] + 5 is UB
19
20 return 0;
21}
Ternary Operator

The conditional (ternary) operator ? :is C's only ternary operator. It provides a concise way to choose between two expressions based on a condition.

ternary.c
C
1#include <stdio.h>
2
3int main(void) {
4 int a = 10, b = 20;
5
6 // Basic ternary
7 int max = (a > b) ? a : b;
8 printf("Max: %d\n", max); // 20
9
10 // Ternary as expression in printf
11 printf("Larger: %d\n", (a > b) ? a : b);
12
13 // Nested ternary (use sparingly)
14 int score = 85;
15 char grade = (score >= 90) ? 'A' :
16 (score >= 80) ? 'B' :
17 (score >= 70) ? 'C' :
18 (score >= 60) ? 'D' : 'F';
19 printf("Grade: %c\n", grade); // B
20
21 // Ternary for absolute value
22 int x = -5;
23 int abs_val = (x < 0) ? -x : x;
24 printf("|%d| = %d\n", x, abs_val);
25
26 // Ternary for safe division check
27 int divisor = 0;
28 int result = (divisor != 0) ? (100 / divisor) : 0;
29 printf("Result: %d\n", result);
30
31 return 0;
32}
📝

note

Unlike C++, the result type of the ternary operator in C is the "common type" of both branches. If the two branches have different types, implicit conversion rules apply. If they are struct or union types, the behavior is more restrictive.
Comma, sizeof, and Pointer Operators

Several operators serve special purposes: the comma operator evaluates expressions left-to-right, sizeof returns type sizes, and pointer operators access addresses and dereference pointers.

special-ops.c
C
1#include <stdio.h>
2
3int main(void) {
4 // Comma operator — evaluates left to right, returns rightmost
5 int x = (1, 2, 3); // x = 3
6 printf("x = %d\n", x);
7
8 // Common use: for loop with multiple counters
9 for (int i = 0, j = 10; i < j; i++, j--) {
10 printf("i=%d, j=%d\n", i, j);
11 }
12
13 // sizeof — returns size in bytes
14 printf("int: %zu bytes\n", sizeof(int));
15 printf("double: %zu bytes\n", sizeof(double));
16
17 // Address-of operator &
18 int val = 42;
19 int *ptr = &val;
20 printf("val = %d, address = %p\n", val, (void *)ptr);
21
22 // Dereference operator *
23 printf("dereferenced: %d\n", *ptr);
24
25 // Modify through pointer
26 *ptr = 100;
27 printf("modified val: %d\n", val); // 100
28
29 // Member access operators
30 struct Point { int x; int y; };
31 struct Point p = {10, 20};
32 struct Point *pp = &p;
33
34 printf("p.x = %d (dot operator)\n", p.x);
35 printf("pp->y = %d (arrow operator)\n", pp->y);
36
37 return 0;
38}
Operator Precedence

The following table lists all C operators from highest to lowest precedence. Operators at the top bind tighter than those at the bottom. When in doubt, use parentheses.

PrecedenceOperatorsAssociativity
1 (highest)() [] -> . ++ -- (postfix)Left-to-right
2++ -- (prefix) + - (unary) ~ ! (type) sizeof & *Right-to-left
3* / %Left-to-right
4+ -Left-to-right
5<< >> (bitwise shift)Left-to-right
6< <= > >=Left-to-right
7== !=Left-to-right
8& (bitwise AND)Left-to-right
9^ (bitwise XOR)Left-to-right
10| (bitwise OR)Left-to-right
11&&Left-to-right
12||Left-to-right
13? :Right-to-left
14= += -= *= /= %= &= |= ^= <<=>>=Right-to-left
15 (lowest), (comma)Left-to-right

info

The most dangerous precedence surprises in C: (1) !a == b is parsed as (!a) == b, not !(a == b). (2) a & b == 0 is parsed as a & (b == 0). (3) *p++ is parsed as *(p++), not (*p)++. Always use parentheses to make intent clear.
Type Casting

C supports explicit type casting with the cast operator (type). Casting forces a conversion that the compiler would not do implicitly, or makes an implicit conversion explicit for clarity.

casting.c
C
1#include <stdio.h>
2
3int main(void) {
4 // Implicit conversions happen automatically
5 int i = 65;
6 char c = i; // implicit: int -> char (truncation)
7 double d = i; // implicit: int -> double (promotion)
8
9 // Explicit cast — makes intent clear
10 double pi = 3.14159;
11 int truncated = (int)pi; // 3 — truncates toward zero
12 printf("(int)3.14159 = %d\n", truncated);
13
14 // Cast for float division
15 int a = 7, b = 2;
16 double result1 = a / b; // 3.0 (int division, then promote)
17 double result2 = (double)a / b; // 3.5 (cast one operand)
18 double result3 = (double)a / (double)b; // 3.5
19 printf("int div: %f\n", result1);
20 printf("cast div: %f\n", result2);
21
22 // Cast for unsigned arithmetic
23 unsigned int u = 4294967295u; // UINT_MAX
24 int s = (int)u; // implementation-defined (usually -1)
25 printf("unsigned->signed: %d\n", s);
26
27 // Cast pointer types (common in memory management)
28 void *vp = &a;
29 int *ip = (int *)vp; // cast void* to int*
30
31 printf("Value through cast pointer: %d\n", *ip);
32
33 return 0;
34}

warning

Avoid casting pointers between incompatible types — it can cause alignment issues and undefined behavior. The cast (int) on a double silently truncates; never rely on implicit narrowing conversions. Use the smallest type that fits your data.
Sequence Points & Undefined Behavior

Sequence points define when all side effects of previous evaluations are complete. Between two sequence points, an object's value can be modified at most once, and it must be read only to determine its value. Violating these rules is undefined behavior.

sequence-points.c
C
1#include <stdio.h>
2
3int main(void) {
4 int x = 1;
5
6 // UNDEFINED BEHAVIOR — modifying x twice between sequence points
7 // x = x++ + 1; // DON'T DO THIS
8
9 // UNDEFINED BEHAVIOR — multiple unsequenced modifications
10 // printf("%d %d\n", x++, x++); // DON'T DO THIS
11
12 // SAFE: sequence points at semicolons and function calls
13 x = 1;
14 x++; // sequence point at ;
15 printf("%d\n", x); // sequence point at function call
16
17 // SAFE: each statement is a sequence point
18 int a = 5;
19 a = a + 1; // OK: read a, then write a (at ;)
20
21 // Common trap: modification in array index and access
22 int arr[] = {10, 20, 30, 40, 50};
23 int i = 0;
24 // arr[i++] = arr[i] + 1; // UNDEFINED — unsequenced read/write of i
25 // Fix: split into separate statements
26 int temp = arr[i];
27 arr[i] = temp + 1;
28 i++;
29
30 printf("arr[0] = %d, i = %d\n", arr[0], i);
31
32 return 0;
33}

warning

The most common undefined behavior traps: (1) modifying a variable twice in one expression (i = i++), (2) reading a variable for its side effect and its value in the same expression (a[i++] = i), (3) using an indeterminate value. Compilers may produce different results — always test with sanitizers.
$Blueprint — Engineering Documentation·Section ID: C-OPERATORS·Revision: 1.0