“Modern C”: Notes on chapter 4 “Expressing computations”
By Dmitry Kabanov
These are my notes taken while reading chapter 4 “Expressing computations” from the book “Modern C” by Jens Gustedt.
This chapter discusses expressions, that is, computations based on some values, such as variables and literals.
The table of contents for all notes for this book are available in that post.
Values used in this chapter are based on type size_t
which is
C representation of non-negative integers (natural numbers and
zero), although on computer this is a finite set of numbers.
Takeaway 4.1. The type size_t
represents values in the range
[0, SIZE_MAX]
.
Constant SIZE_MAX
comes from the header file stdint.h
.
Previously, computers had 32-bit processors and the value of
SIZE_MAX
was about 4 billions (precisely, \(2^{32} - 1\), where
“minus 1” is to accomodate zero).
Nowadays, most processors are 64-bit and SIZE_MAX
is much larger
(exact value is \(2^{64} - 1\), which is a number with 20 digits).
4.1 Arithmetic
Arithmetic operators +
, -
, *
, /
work in general
as we expect from mathematics.
For unsigned values such as variables of type size_t
the result
is always well-defined.
When performing arithmetic, division /
and modulus %
are
well-defined only if the second operand is not 0.
Sometimes the result of an arithmetic operation is such that
it is not within range [0, SIZE_MAX~], for example, it can become negative or larger than ~SIZE_MAX
(overflow).
In such cases, arithmetic on size_t
values “wraps around”,
that is, the result is replace with the remainder of modulus
operation % (SIZE_MAX + 1)
.
For example, -1 becomes SIZE_MAX
, while SIZE_MAX + 1
becomes 0.
4.2 Operators that modify objects
The first example is the assignment operator =
.
In C terminology, the right side is called rvalue and the left
side is called lvalue.
C has compact assignment operators for such expressions:
var = var O expression
which can be replaced with
var O= expression
where O
is +
, -
, *
, /
.
Also, often used operators are increment i++
and ++i
and
decrement i--
and --i
. Postfix forms first return the value of
i
and then modify it, while prefix forms first modify i
and
then return the new value.
It is not recommended to use these operators in more complex
expressions directly but always as separate statements to improve
code readability.
4.3 Boolean context
Boolean expressions use comparison and logic operators
and always return values false
or true
.
Note that false
and true
in C are the same as 0 and 1,
respectively, so they can be used in arithmetic expressions.
Comparison operators are: equal to ==
, not equal to !=
, less than <
,
less or equal to <=
, greater than >
, greater or equal to >=
.
Logic operators are: not !
, logical and &&
, logical or ||
.
Logical and and or use “short-curcuit evaluation”, so that if the value of a logical expression can be determined from one of the subexpressions, other subexpressions are not evaluated:
if ((b != 0) && ((a/b) > 1)) {
// Subexpressions (a/b) > 1 is not evaluated at all
// if b equals to zero.
}
4.4 The ternary or conditional operator
The ternary operator is a compact form from an if-else
statement
with assignment. For example, this is how a minimum of two numbers
can be computed:
min_a_b = (a < b) ? a : b;
4.5 Evaluation order
C has comma operator, such that expression f(a), f(b)
first
evaluates f(a)
and then evaluates f(b)
and returns the value
of f(b)
.
The comma operator is rarely useful and is confusing.
For example, in A[i, j]
the result is A[j]
.
Takeaway 4.16. Do not use comma operator.
Most operators do not have a predefined order, so that the result
can be different depending on the order: for example, if
function f
changes some global state, the result of f(a) + f(b)
probably depends on the order in which they are evaluated.
Takeaway 4.19. Functions that are called inside expressions should not have side effects.
This is the only way to preserve sanity while programming in C.