Operators, Expressions and Conversions
Arithmetic, comparison, logical and bitwise operators, integer division and remainder, precedence, and the implicit conversions — integer promotion and the usual arithmetic conversions — that surprise newcomers.
Arithmetic operators
C has the usual
arithmetic operators: +, -, *, /,
and % for the remainder. There is no operator for
powers (^ is bitwise XOR, below); use
pow from <math.h> or multiply.
Integer division and remainder
When both operands are integers, / is
integer division: the result is an integer, and since C99 the fraction is always
cut off toward zero. The remainder a % b is defined so
that (a / b) * b + a % b equals a, which
gives it the sign of the left operand. As soon as one operand is a
floating-point number, the division is a normal one:
#include <stdio.h>
int main(void)
{
int a = 7, b = 2;
printf(" 7 / 2 = %d 7 %% 2 = %d\n", a / b, a % b);
printf("-7 / 2 = %d -7 %% 2 = %d\n", -a / b, -a % b);
printf(" 7 / -2 = %d 7 %% -2 = %d\n", a / -b, a % -b);
printf(" 7 / 2.0 = %.1f\n", a / 2.0);
printf("(double)7 / 2 = %.1f\n", (double)a / b);
printf("5 / 2 * 2.0 = %.1f\n", 5 / 2 * 2.0);
return 0;
}
7 / 2 = 3 7 % 2 = 1 -7 / 2 = -3 -7 % 2 = -1 7 / -2 = -3 7 % -2 = 1 7 / 2.0 = 3.5 (double)7 / 2 = 3.5 5 / 2 * 2.0 = 4.0
(%% inside a printf format prints a single
%.) The last line is a typical surprise:
5 / 2 is evaluated first, as integers, giving 2; only
then is it multiplied by 2.0. Integer division and the
remainder together split quantities into units, for example minutes
into hours, or rounding up to whole quarter-hours, a common step
with meter data:
#include <stdio.h>
int main(void)
{
int minutes = 135;
int hours = minutes / 60;
int rest = minutes % 60;
int quarter_hours = (minutes + 14) / 15; /* round up */
printf("%d min = %d h %d min, %d quarter-hours\n",
minutes, hours, rest, quarter_hours);
return 0;
}
135 min = 2 h 15 min, 9 quarter-hours
Dividing an integer by zero is undefined behavior, and what you see
depends on the processor. On x86-64 Linux the program usually dies
with "Floating point exception" (the signal SIGFPE, despite the
name). On ARM64 the divide instruction simply returns 0: on the test
machine, 1000 / zero printed 0 and the
program carried on, which is worse, because nothing looks wrong.
UndefinedBehaviorSanitizer reports it as
runtime error: division by zero. Check the divisor
first. Floating-point division by zero gives infinity or NaN, as in
IEEE 754.
Increment, decrement and compound assignment
The
increment and decrement operators
++ and -- add or subtract 1 and store the
result in the variable. They come in two forms with a different
value: the postfix form k++ gives the old value, the
prefix form ++k the new one. Compound assignments such
as +=, -=, *= and
/= combine an operation with storing the result.
#include <stdio.h>
int main(void)
{
int k = 5;
int r = k++ + 10; /* uses 5, then k becomes 6 */
printf("r = %d, k = %d\n", r, k);
int m = 5;
int s = ++m + 10; /* m becomes 6 first, then 16 */
printf("s = %d, m = %d\n", s, m);
int total = 100;
total += 25; /* total = total + 25 */
total *= 2;
printf("total = %d\n", total);
return 0;
}
r = 15, k = 6 s = 16, m = 6 total = 250
Used on its own, as in i++; or the third part of a
for loop, both forms do the same thing. Python has no
++; in Python ++k is just two unary plus
signs.
Order of evaluation and sequence points
C does not say in which order the operands of most operators are
evaluated: in f() + g(), either function may run first.
What C does define are points in the program where all side effects
so far must be complete. Such a
sequence point lies, among
others, at the end of each full expression (the ;),
after the left operand of &&, ||,
?: and the comma operator, and before a function is
called. Since C11 the standard describes the same rules as
"sequenced before" relations. The rule that matters in practice:
between two sequence points, do not modify a variable twice, and do
not read it apart from computing its new value. Otherwise the
behavior is undefined. GCC's -Wall catches the simple
cases:
#include <stdio.h>
int main(void)
{
int i = 1;
i = i++ + 1;
printf("%d\n", i);
return 0;
}
example.c:6:7: error: operation on ‘i’ may be undefined [-Werror=sequence-point]
Other compilers may give 2, 3 or something else for code like this,
and all of them are right, because the standard requires nothing.
The same goes for printf("%d %d", n++, n); and
a[i] = i++;. The fix is always to split the work into
separate statements.
Comparison and logical operators
The comparison operators ==, !=,
<, <=, > and
>= and the logical operators
&& (and), || (or) and
! (not) produce an int: 1 for true, 0 for
false. In a condition, any value that is not zero counts as true.
&& and || use
short-circuit evaluation: the right operand is only evaluated when it can still change the
result, which makes guards like "not zero and …" safe:
#include <stdio.h>
int main(void)
{
int kwh = 350;
int is_high = kwh > 300;
int ok = kwh > 0 && kwh < 10000;
printf("kwh > 300 is %d, in range is %d, !ok is %d\n", is_high, ok, !ok);
printf("10 == 10.0 is %d\n", 10 == 10.0);
int readings = 0;
if (readings != 0 && 1000 / readings > 5) { /* no division by zero */
printf("high average\n");
} else {
printf("no readings yet\n");
}
return 0;
}
kwh > 300 is 1, in range is 1, !ok is 0 10 == 10.0 is 1 no readings yet
10 == 10.0 is true because the int is
converted to double before comparing (see
usual arithmetic conversions). Comparing results of floating-point calculations with
== is unreliable for the reasons on
Floating-Point Numbers. Chained comparisons do not work as in Python:
0 < kwh < 10000 compiles, but means
(0 < kwh) < 10000, which is always true, since
the left part is 0 or 1. Write
kwh > 0 && kwh < 10000.
Because an assignment is also an expression, a single
= in a condition is valid C and a classic bug: it
assigns instead of comparing, and the condition is then the assigned
value. -Wall asks for extra parentheses if you really
mean it:
#include <stdio.h>
int main(void)
{
int status = 0;
if (status = 1) {
printf("status is one\n");
}
return 0;
}
example.c:6:9: error: suggest parentheses around assignment used as truth value [-Werror=parentheses]
Bitwise operators and shifts
The
bitwise operators
work on the individual bits of integers: & (and),
| (or), ^ (exclusive or),
~ (not), and the shifts << and
>>. They are everyday tools in firmware, network
code and file formats: testing a flag in a status byte, setting a
bit in a hardware register, packing values. The theory, with masks,
is on
Binary Arithmetic and Bitwise Operations; setting, clearing and toggling register bits in C is shown on
Firmware and Embedded Programming.
#include <stdio.h>
int main(void)
{
unsigned a = 0x0F, b = 0x3C;
printf("a & b = %u\n", a & b);
printf("a | b = %u\n", a | b);
printf("a ^ b = %u\n", a ^ b);
printf("1u << 31 = %u\n", 1u << 31);
printf("0xF0 >> 4 = %u\n", 0xF0u >> 4);
int negative = -8;
printf("-8 >> 1 = %d\n", negative >> 1);
return 0;
}
a & b = 12 a | b = 63 a ^ b = 51 1u << 31 = 2147483648 0xF0 >> 4 = 15 -8 >> 1 = -4
Three rules keep bit manipulation safe:
-
Use unsigned types. Shifting a negative number
right is implementation-defined (GCC copies the sign bit, giving
−4 above; another compiler may not), and shifting a 1 into or past
the sign bit of a signed type is undefined. Write
1u << 31, not1 << 31. -
Never shift by the width of the type or more.
1u << 32on a 32-bitunsignedis undefined behavior. Shift1uby 0 to 31 only, or use a 64-bit type such asUINT64_C(1) << n. -
Mind
~on small types. The operand is promoted tointfirst; see integer promotion below.
The compiler warns about a constant shift that is too large, but cannot see a shift count that arrives at run time. The sanitizer can:
#include <stdio.h>
int main(void)
{
unsigned bits = 32; /* imagine this came from a config file */
printf("building a mask of bit %u\n", bits);
unsigned mask = 1u << bits; /* only 0..31 are allowed */
(void)mask;
return 0;
}
building a mask of bit 32 example.c:7:24: runtime error: shift exponent 32 is too large for 32-bit type 'unsigned int'
Without the sanitizer this program runs without complaint; on the
test machine the same shift gave 1 at -O0 and 0 at
-O2, a typical sign of undefined behavior (see
Undefined Behavior and Safer C).
The conditional operator
The
conditional operator
condition ? a : b is C's only operator with three
operands: it evaluates the condition, then only one of the other
two, and gives its value. It is handy for short choices inside an
expression; for anything longer, an if statement is
easier to read (see
Control Flow and Loops).
#include <stdio.h>
int main(void)
{
int kwh = 350;
const char *label = kwh > 300 ? "high" : "normal";
int abs_diff = kwh > 400 ? kwh - 400 : 400 - kwh;
printf("%d kWh is %s, %d from the limit\n", kwh, label, abs_diff);
printf("%d reading%s\n", 1, 1 == 1 ? "" : "s");
return 0;
}
350 kWh is high, 50 from the limit 1 reading
The comma operator a, b evaluates a, then
b, and gives the value of b. You see it
almost only in for loops with two counters (for (i = 0, j = n - 1; i < j; i++, j--)). The commas between function arguments are not comma operators.
Precedence and associativity
Operator precedence
decides how an expression without parentheses is grouped. C has 15
levels; the table shows the most used, from binding most tightly to
least. Operators on the same row are grouped from left to right,
except the unary operators, ?: and assignment, which
group from right to left (so a = b = 0 sets both).
| Level | Operators |
|---|---|
| Postfix |
a[i] f() .
-> x++ x--
|
| Unary |
++x --x !
~ unary - *p
&x (type) sizeof
|
| Multiplicative | * / % |
| Additive | + - |
| Shift | << >> |
| Relational |
< <= >
>=
|
| Equality | == != |
| Bitwise |
&, then ^, then
| (three levels)
|
| Logical |
&&, then || (two levels)
|
| Conditional | ?: |
| Assignment |
= += -=
*= /= %=
&= |= ^=
<<= >>=
|
| Comma | , |
The bitwise operators bind less tightly than the
comparisons, a historical accident that catches everyone once.
status & ALARM_BIT == 0 means
status & (ALARM_BIT == 0), which is always 0. GCC
warns:
#include <stdio.h>
#define ALARM_BIT 0x04u
int main(void)
{
unsigned status = 0x06u;
if (status & ALARM_BIT == 0) {
printf("no alarm\n");
}
return 0;
}
example.c:8:16: error: suggest parentheses around comparison in operand of ‘&’ [-Werror=parentheses]
Write (status & ALARM_BIT) == 0. As a habit: add
parentheses whenever you mix bitwise operators, shifts or
&& and ||; nobody remembers the
whole table, and readers should not have to.
Implicit conversions
C converts values from one type to another automatically in many places: in assignments, when passing arguments, and before most arithmetic. Such an implicit conversion happens without any operator in the code, which is convenient and is the source of many C surprises. Two sets of rules decide what happens in arithmetic.
Integer promotion
C never does arithmetic in types smaller than int.
Under integer promotion, a
char, short, _Bool or bit
field (signed or unsigned) is converted to int before
an operator works on it, when an int can hold all its
values (otherwise to unsigned int, e.g.
unsigned short on 16-bit microcontrollers). The result
therefore has type int, and only shrinks again when you
store it in a smaller variable:
#include <stdint.h>
#include <stdio.h>
int main(void)
{
unsigned char a = 200, b = 100;
int sum = a + b; /* both promoted to int: 300 */
unsigned char stored = a + b; /* 300 does not fit: 44 */
printf("sum %d, stored %d\n", sum, stored);
uint8_t x = 0xF0;
printf("~x = %d\n", ~x); /* promoted to int first */
printf("(uint8_t)~x = %d\n", (uint8_t)~x);
return 0;
}
sum 300, stored 44 ~x = -241 (uint8_t)~x = 15
The sum of two unsigned char values does not wrap at
255: it is computed as int. Storing 300 in an
unsigned char keeps the low 8 bits, 44. And
~x flips all 32 bits of the promoted
int 0x000000F0, giving 0xFFFFFF0F, which is −241. To
get the 8-bit result 0x0F, convert back with a cast. Firmware code
that inverts or shifts uint8_t and
uint16_t values, as in a CRC calculation, is full of
such casts for this reason.
The usual arithmetic conversions
When the two operands of an arithmetic or comparison operator have different types after promotion, the usual arithmetic conversions bring them to one common type. Simplified:
-
If either operand is a floating-point type, the other is converted
to the larger floating-point type (
intanddouble→double). - Otherwise, if both are signed or both unsigned, the smaller type is converted to the larger.
- Otherwise, with one signed and one unsigned operand: if the unsigned type is at least as large, the signed value is converted to that unsigned type. If the signed type is larger and can hold every value of the unsigned one, the unsigned value is converted to the signed type.
Rule 3 is the dangerous one. Comparing an int with an
unsigned int converts the int to unsigned,
so −1 becomes 4,294,967,295. A negative balance is then "not below"
a limit of 10. With -Wextra, GCC refuses this
comparison:
#include <stdio.h>
int main(void)
{
int balance = -1;
unsigned limit = 10;
if (balance < limit) {
printf("below limit\n");
}
return 0;
}
example.c:8:17: error: comparison of integer expressions of different signedness: ‘int’ and ‘unsigned int’ [-Werror=sign-compare]
The warning only covers comparisons. Arithmetic with mixed types is
converted the same way, silently, and a size_t (as
returned by strlen) makes it easy to get there:
#include <stdio.h>
#include <string.h>
int main(void)
{
const char *code = "ab";
if (strlen(code) - 3 > 0) {
printf("strlen(code) - 3 = %zu\n", strlen(code) - 3);
printf("so a 2-character code counts as longer than 3\n");
}
if ((long)strlen(code) - 3 > 0) {
printf("never printed\n");
} else {
printf("with a signed type: %ld\n", (long)strlen(code) - 3);
}
return 0;
}
strlen(code) - 3 = 18446744073709551615 so a 2-character code counts as longer than 3 with a signed type: -1
strlen(code) - 3 is computed in size_t,
which is unsigned, so 2 − 3 wraps around to the largest 64-bit
value, and that is certainly greater than 0. Compare lengths
directly instead: strlen(code) > 3. Whether a mixed
comparison is dangerous can even depend on the platform. On LP64,
long is larger than unsigned int, so
long c = -1; c < 10u is true (the unsigned value
becomes a long); on 64-bit Windows, where
long is 32 bits, the same comparison is false.
Casts and explicit conversions
The
cast operator
(type) value converts a value to another type on
purpose. It is how you ask for a floating-point division of two
integers, keep an 8-bit result, or tell the compiler that a
conversion that may lose information is intended. A conversion to a
smaller type, like the Java
narrowing conversion, can lose information; C does it without complaint once you write
the cast.
#include <stdio.h>
int main(void)
{
printf("(int)3.99 = %d\n", (int)3.99);
printf("(int)-3.99 = %d\n", (int)-3.99);
printf("'7' - '0' = %d\n", '7' - '0');
printf("'A' + 1 = %c\n", 'A' + 1);
int used = 7, days = 2;
printf("average = %.2f\n", (double)used / days);
return 0;
}
(int)3.99 = 3 (int)-3.99 = -3 '7' - '0' = 7 'A' + 1 = B average = 3.50
-
Converting a floating-point value to an integer truncates toward
zero; it does not round. Use
lroundto round. If the value does not fit in the integer type at all, the behavior is undefined. -
Characters are small integers. Digits
'0'to'9'have consecutive codes, soc - '0'turns a digit character into its value, a common step when parsing a meter telegram by hand (see ASCII). -
The cast binds tightly:
(double)used / daysconvertsusedfirst, and the division is then done indouble.(double)(used / days)would convert the already truncated result 3.