Operators: Arithmetic, Comparison, Logical
This chapter covers arithmetic, comparison, and logical operators: their semantics, their results, and their common traps. These are the operators you use constantly, so they are concise but precise.
Why This Matters
These operators look like their counterparts in other languages, but C's integer arithmetic has overflow rules and its logical operators have specific "truthiness" and short-circuit semantics that differ from languages with real booleans.
Prerequisites
c.core.8— expressions and evaluation.c.core.3— integer types (for arithmetic and overflow).
Core Concept
Arithmetic operators
| Operator | Meaning | Notes |
|---|---|---|
+ | addition | signed overflow is UB; unsigned wraps |
- | subtraction | same overflow rules |
* | multiplication | same overflow rules |
/ | division | division by zero is UB |
% | remainder | % is not defined for negative operands in C89; defined since C99 |
For / and %: if both operands are non-negative, the result is non-negative. Since C99, the result of / truncates toward zero, and a % b has the sign of a. Division by zero, and INT_MIN / -1, are undefined behavior.
Comparison operators
| Operator | Meaning |
|---|---|
== | equal |
!= | not equal |
< | less than |
> | greater than |
<= | less than or equal |
>= | greater than or equal |
Comparison operators yield 1 (true) or 0 (false), of type int. They follow the usual arithmetic conversions (c.types.uac), so comparing signed and unsigned values has specific, often surprising rules.
Logical operators
| Operator | Meaning | ||
|---|---|---|---|
! | logical NOT | ||
&& | logical AND | ||
| ` | ` | logical OR |
Logical operators treat any nonzero value as true and zero as false. !x yields 1 if x is zero, else 0. && and || yield 1 or 0, and they short-circuit: the right operand is evaluated only if needed.
Syntax
a + b;
a - b;
a * b;
a / b;
a % b;
a == b; a != b; a < b; a > b; a <= b; a >= b;
!a;
a && b;
a || b;
Examples
Arithmetic
#include <stdio.h>
int main(void)
{
printf("%d\n", 7 / 2); /* 3: truncates toward zero */
printf("%d\n", -7 / 2); /* -3 (C99 and later) */
printf("%d\n", 7 % 2); /* 1 */
printf("%d\n", -7 % 2); /* -1 (C99 and later: sign of dividend) */
return 0;
}
Expected output (C99 and later):
3
-3
1
-1
Short-circuit evaluation
#include <stdio.h>
int is_positive(int x) { printf("checking %d\n", x); return x > 0; }
int main(void)
{
int a = 0;
int b = 5;
if (a != 0 && is_positive(b)) {
/* not executed because a != 0 is false */
}
/* is_positive(b) was never called */
return 0;
}
Because a != 0 is false, is_positive(b) is not evaluated.
How It Works
Arithmetic and comparison operators first apply the usual arithmetic conversions (c.types.uac) to bring both operands to a common type, then compute the result in that type. Logical operators evaluate to int with value 0 or 1.
Variations
Logical vs. bitwise
Do not confuse &&/|| with &/|:
a && b; /* logical AND: 0 or 1, short-circuits */
a & b; /* bitwise AND: bit-by-bit, no short-circuit */
This distinction is a frequent source of bugs (covered in c.core.10).
Common Mistakes
- Using
=instead of==in a condition. (Some compilers warn with
-Wparentheses or similar.)
- Using
&/|where&&/||is intended (loses short-circuiting). - Assuming
%with negative operands works the same across standards. - Ignoring signed overflow (UB) in arithmetic.
Undefined Behavior
- Signed integer overflow (
+,-,*) is UB.VERIFIED - Division by zero is UB for both signed and unsigned.
VERIFIED INT_MIN / -1is UB (result not representable).VERIFIED- In C89,
%with negative operands was implementation-defined; C99 defines it
to follow the dividend's sign. STANDARD-VERSION-DEPENDENT
Portability
- Integer overflow behavior differs: unsigned wraps (portable), signed is UB
(must avoid).
- The C89/C99 difference in
%and/negative semantics is a portability
concern only for very old or non-conforming compilers.
Under the Hood
Arithmetic maps to CPU integer instructions. Comparison maps to compare instructions that set condition flags, which branch instructions then test. Logical operators compile to conditional branches (for short-circuiting) or to compare-and-set sequences.
Practical Usage
- Use explicit checks before operations that might overflow (or use unsigned
arithmetic where wrapping is desired).
- Prefer
&&/||for conditions and&/|only when you truly want bitwise
behavior.
Exercises
1. Write a program that prints 7/2, -7/2, 7%2, and -7%2 and verify the C99 behavior. 2. Demonstrate short-circuiting with a function that prints when called. 3. Write a safe addition function that returns an error on signed overflow without invoking UB.
Deep Challenge
Explain why INT_MIN / -1 is undefined behavior but INT_MIN % -1 is also undefined, while 0 / 1 and 0 % 1 are fine. Relate your answer to the representability of the result.
Related Concepts
c.core.10— bitwise and shift operators.c.core.13— precedence and sequencing.c.types.uac— usual arithmetic conversions.c.sec.2— integer overflow in security.
References
- ISO/IEC 9899:2018 §6.5.5 (multiplicative), §6.5.6 (additive), §6.5.8–9
(relational/equality), §6.5.13–14 (logical).
Verification
- Signed overflow UB; unsigned wraps.
VERIFIED - Division by zero UB.
VERIFIED - C99
/truncation and%sign rules.VERIFIED - Logical operators yield 0 or 1 and short-circuit.
VERIFIED - No example was executed during generation unless noted. `Execution not
verified.`
Progress
- [ ] Read
- [ ] Understand
- [ ] Complete examples
- [ ] Complete exercises
- [ ] Complete deep challenge
Concept checkboxes
- [ ] Arithmetic operators
- [ ] Comparison operators
- [ ] Logical operators and short-circuiting
Mastery levels
| Concept | Current level (0–8) | Target level |
|---|---|---|
| c.ops.arith | 0 | 5 |
| c.ops.compare | 0 | 5 |
| c.ops.logical | 0 | 5 |