ZetCode

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.

arithmetic.c
#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.

modulo_sign.c
#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.

precedence.c
#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.

comparison.c
#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.

logical.c
#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.

assignment.c
#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.

increment.c
#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.

ternary.c
#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

My name is Jan Bodnar, and I'm a dedicated programmer with a deep passion for coding. Since 2007, I've been sharing my expertise through over 1,400 articles and 8 e-books. With more than a decade of teaching experience, I strive to make programming accessible and engaging.

List all C tutorials.