C operators
last modified November 14, 2026
An expression is built from operands and operators, and its value and type depend on both. C has a compact set of operators: arithmetic, comparison, logical, assignment, increment and the conditional operator, plus the bitwise operators that have an article of their own. This tutorial covers how they compute, how they combine according to precedence and associativity, and where the rules produce surprising results.
Arithmetic operators
The binary operators +, -, *,
/ and % work on integers and, except for
%, on floating-point values. The unary + and
- change the sign of a single operand.
#include <stdio.h>
int main(void) {
int a = 17;
int b = 5;
printf("%d + %d = %d\n", a, b, a + b);
printf("%d - %d = %d\n", a, b, a - b);
printf("%d * %d = %d\n", a, b, a * b);
printf("%d / %d = %d\n", a, b, a / b);
printf("%d %% %d = %d\n", a, b, a % b);
return 0;
}
Because both operands are int, the division is integer division:
17 divided by 5 is 3 and the remainder 2 is discarded. The
% operator returns that remainder, and it is the reason the format
string uses %% to print a literal percent sign.
$ gcc -std=c17 -Wall -Wextra -o arithmetic arithmetic.c $ ./arithmetic 17 + 5 = 22 17 - 5 = 12 17 * 5 = 85 17 / 5 = 3 17 % 5 = 2
Division and the sign of the remainder
Since C99, integer division truncates toward zero, so the quotient of -17 and 5
is -3 and not -4. The remainder always satisfies
(a / b) * b + a % b == a, which fixes its sign as the sign of the
dividend.
#include <stdio.h>
int main(void) {
printf("-17 / 5 = %d\n", -17 / 5);
printf("-17 %% 5 = %d\n", -17 % 5);
printf("17 / -5 = %d\n", 17 / -5);
printf("17 %% -5 = %d\n", 17 % -5);
return 0;
}
The remainder takes the sign of the left operand: -2 for -17 and 2 for 17, regardless of the sign of the divisor. This differs from the mathematical modulus, which is always non-negative for a positive divisor, so code that needs a wrapped index must add the divisor when the result is negative.
$ gcc -std=c17 -Wall -Wextra -o modulo_sign modulo_sign.c $ ./modulo_sign -17 / 5 = -3 -17 % 5 = -2 17 / -5 = -3 17 % -5 = 2
Precedence and associativity
When an expression contains several operators, precedence decides which binds
tighter and associativity decides the order among operators of equal
precedence. Multiplication and division bind tighter than addition and
subtraction, and assignment is right-associative, so a = b = c
assigns c to b and then b to
a.
#include <stdio.h>
int main(void) {
int r1 = 2 + 3 * 4;
int r2 = (2 + 3) * 4;
int a, b, c;
a = b = c = 7;
printf("2 + 3 * 4 = %d\n", r1);
printf("(2 + 3) * 4 = %d\n", r2);
printf("a, b, c = %d, %d, %d\n", a, b, c);
printf("!0 + 1 = %d\n", !0 + 1);
printf("-2 * -3 = %d\n", -2 * -3);
return 0;
}
The first expression is 14 because * is evaluated before
+; the parentheses in the second force 20. Unary operators bind
tighter than the binary ones, so !0 + 1 is (!0) + 1,
which is 2, and the unary minus in -2 * -3 applies to each operand
separately. When in doubt, add parentheses: they cost nothing and remove the
need to remember a table.
$ gcc -std=c17 -Wall -Wextra -o precedence precedence.c $ ./precedence 2 + 3 * 4 = 14 (2 + 3) * 4 = 20 a, b, c = 7, 7, 7 !0 + 1 = 2 -2 * -3 = 6
Comparison operators
The operators ==, !=, <,
<=, > and >= compare two values
and produce an int that is 1 for true and 0 for false. There is no
separate boolean type for the result.
#include <stdio.h>
int main(void) {
int x = 3;
printf("x > 2: %d\n", x > 2);
printf("x == 3: %d\n", x == 3);
printf("1 < 2 < 3: %d\n", 1 < 2 < 3);
printf("3 < 2 < 1: %d\n", 3 < 2 < 1);
return 0;
}
Both chained comparisons print 1, although the second is mathematically false.
The operators are left-associative, so 3 < 2 < 1 is
(3 < 2) < 1, which is 0 < 1 and therefore
true. GCC recognizes the pattern and warns that the expression does not have
its mathematical meaning. Write the conjunction explicitly:
1 < 2 && 2 < 3.
$ gcc -std=c17 -Wall -Wextra -o comparison comparison.c
comparison.c: In function ‘main’:
comparison.c:9:37: warning: comparisons like ‘X<=Y<=Z’ do not have their mathematical meaning [-Wparentheses]
9 | printf("1 < 2 < 3: %d\n", 1 < 2 < 3);
| ^
comparison.c:10:37: warning: comparisons like ‘X<=Y<=Z’ do not have their mathematical meaning [-Wparentheses]
10 | printf("3 < 2 < 1: %d\n", 3 < 2 < 1);
| ^
$ ./comparison
x > 2: 1
x == 3: 1
1 < 2 < 3: 1
3 < 2 < 1: 1
Logical operators and short-circuit evaluation
&& is true when both operands are true,
|| when at least one is, and ! negates a single
operand. Both binary operators evaluate their left operand first and skip the
right one when the result is already decided.
#include <stdio.h>
int check(const char *name, int value) {
printf(" evaluating %s\n", name);
return value;
}
int main(void) {
int r1 = check("a", 0) && check("b", 1);
int r2 = check("c", 1) || check("d", 0);
printf("r1: %d\n", r1);
printf("r2: %d\n", r2);
return 0;
}
The output contains only a and c: once
check("a", 0) returns 0 the conjunction cannot be true, and once
check("c", 1) returns 1 the disjunction cannot be false. This is
short-circuit evaluation, and programs rely on it to guard pointer
checks and division, as in p != NULL && *p == 0. The
operators do not have to produce 1 or 0 themselves: any non-zero value counts as
true, and the result is always 0 or 1.
$ gcc -std=c17 -Wall -Wextra -o logical logical.c $ ./logical evaluating a evaluating c r1: 0 r2: 1
Assignment operators
Besides the plain =, C has compound assignments that combine an
operation with storing the result: +=, -=,
*=, /=, %= and the bitwise variants.
x += 5 is equivalent to x = x + 5, except that the
left operand is evaluated only once.
#include <stdio.h>
int main(void) {
int x = 10;
x += 5;
printf("after += 5: %d\n", x);
x *= 2;
printf("after *= 2: %d\n", x);
x -= 6;
printf("after -= 6: %d\n", x);
int y = 3;
y *= 2 + 1;
printf("y *= 2 + 1: %d\n", y);
return 0;
}
The last case is a common trap. The right side of a compound assignment is the
whole expression that follows it, so y *= 2 + 1 multiplies by 3 and
not by 2 before adding 1. An assignment is itself an expression whose value is
the value stored, which is what makes the chained form in the previous section
work.
$ gcc -std=c17 -Wall -Wextra -o assignment assignment.c $ ./assignment after += 5: 15 after *= 2: 30 after -= 6: 24 y *= 2 + 1: 9
Increment and decrement
++ and -- add or subtract one. In the prefix form the
value of the expression is the new value; in the postfix form it is the old
value, although the variable is updated either way. Both require an lvalue, an
object that can be assigned to.
#include <stdio.h>
int main(void) {
int i = 5;
int a = i++;
printf("a = %d, i = %d\n", a, i);
int b = ++i;
printf("b = %d, i = %d\n", b, i);
i--;
printf("after i--: %d\n", i);
return 0;
}
i++ stores 5 in a and then increments i,
while ++i increments first and stores 7 in b. Using
both forms in one expression, as in i = i++ + 1, modifies the same
object twice without a sequence point; the behavior is undefined and the
compiler is free to produce any result.
$ gcc -std=c17 -Wall -Wextra -o increment increment.c $ ./increment a = 5, i = 6 b = 7, i = 7 after i--: 6
The conditional operator
?: is the only ternary operator: it evaluates a condition and then
exactly one of two expressions. Its result has the common type of the two
branches, which is why a double and an int branch
produce a double.
#include <stdio.h>
int main(void) {
int a = 7;
int b = 12;
int max = a > b ? a : b;
const char *kind = a % 2 == 0 ? "even" : "odd";
printf("max: %d\n", max);
printf("a is %s\n", kind);
printf("mixed: %.1f\n", 1 ? 2.5 : 3);
return 0;
}
The operator is useful for choosing between two values, as in
max and kind, and it keeps such a choice inside an
expression. It is a poor substitute for an if statement when the
branches have side effects or are long, because the condition and the two
results then become hard to read. In the last line the branches
2.5 and 3 have different types, so both are converted
to double.
$ gcc -std=c17 -Wall -Wextra -o ternary ternary.c $ ./ternary max: 12 a is odd mixed: 2.5
Source
cppreference: operator precedence
cppreference: expressions
cppreference: logical operators
cppreference: increment and decrement
This tutorial covered the operators of C: arithmetic and the sign of the remainder, precedence and associativity, comparisons that return 0 or 1, short-circuit evaluation of the logical operators, compound assignment, the two forms of increment, and the conditional operator. The operators that work on individual bits are described in C bitwise operators, and the integer types they combine in C data types.
Author
List all C tutorials.