Chapter 4: Expressing computations
Introduces C expressions as code that computes values and distinguishes arithmetic expressions from comparisons.
After this chapter you will be able to
- Expressions and their kinds. Explain that a C expression computes a value from other values, and give arithmetic and comparison expressions as examples.
- The size_t value range. Explain that size_t holds non-negative integers from 0 through SIZE_MAX, rather than every natural number, and that <stdint.h> supplies SIZE_MAX.
- Operators and their operands. Distinguish an operator from its operands using an expression such as a + b.
- Binary arithmetic and grouping. Describe what binary +, -, and * compute and how parentheses can group sub-expressions to enforce a preferred binding.
- Representable size_t arithmetic. Explain the guarantee for +, -, and * on size_t: their result is mathematically correct when it falls within [0, SIZE_MAX], and a result outside that range is arithmetic overflow.
- Division and remainder relationship. Explain what unsigned division and remainder compute and how their results reconstruct the original value.
- Unsigned arithmetic wraparound. Explain how an out-of-range unsigned result is reduced and apply that rule to size_t at the ends of its range.
- Assignment and compound assignment. Explain that assignment places a value into an object, while compound assignment combines an arithmetic operation with assignment. State that an assignment operator yields the object's value after modification, not the object itself.
- Prefix and postfix results. Distinguish prefix from postfix increment and decrement by the value each provides to a surrounding expression, while noting that both forms modify the object in the same way.
- Side effects and expression clarity. Explain why the section discourages modifying objects within complicated value expressions and recommends modifying no more than one object in a statement.
- Boolean operator results and categories. Explain that several operators yield 0 or 1 depending on whether a condition is verified, and identify comparisons and logical evaluation as the two categories.
- Comparison results and boundaries. Distinguish strict comparisons from inclusive comparisons and explain that comparison operators produce false or true.
- Logical operators and truth values. Explain what !, &&, and || mean and that their results are the truth values 0 or 1. Describe how applying ! twice turns a value into a truth value that can be used as an array index.
- Short-circuit operand evaluation. Explain that && and || omit evaluation of their second operand when the first already determines the result. Describe why this can prevent the example’s division by zero.
- Conditional expression branch selection. Explain how the ternary operator differs from an if statement and why its unchosen branch is not evaluated. Describe how this keeps the section’s sqrt_real example from passing a negative argument to sqrt.
- Guaranteed conditional evaluation order. Explain that &&, ||, and ?: evaluate their first operand first because it determines whether other operands are evaluated.
- Unsequenced expressions and side effects. Explain that most operators do not specify the order of their operands and function calls do not specify the order of argument expressions. Justify avoiding side effects in function calls within expressions when the result might otherwise depend on that order.
What it builds on
- Conditional execution (chapter 3) · Earlier use of comparisons in control statements.
Section by section
Introduction
Introduces C expressions as code that computes values and distinguishes arithmetic expressions from comparisons.
- Expressions and their kinds: Explain that a C expression computes a value from other values, and give arithmetic and comparison expressions as examples.
Why it matters: This gives you a starting point for recognizing the computations discussed throughout the chapter.
Key terms: expressions, arithmetic expressions, comparison operators
4.1 Operands and operators
This section introduces the range of size_t, names operators and operands, and shows how evaluating an object supplies a value for an operation.
- The size_t value range: Explain that size_t holds non-negative integers from 0 through SIZE_MAX, rather than every natural number, and that <stdint.h> supplies SIZE_MAX.
Why it matters: The bound tells you which sizes a size_t value can represent. - Operators and their operands: Distinguish an operator from its operands using an expression such as a + b.
Why it matters: This vocabulary makes it easier to describe the expressions examined throughout the chapter. - Object evaluation before arithmetic: Trace the section's a + 5 example: a is evaluated as an object, then its value and 5 are combined by the arithmetic operator.
Why it matters: It connects a variable used in an expression with the value on which arithmetic operates.
Key terms: size_t, SIZE_MAX, unsigned integer types, <stdint.h>, operator, operand, object, value, evaluated, arithmetic operation
4.2 Arithmetic
4.2.1 +, -, and *
This section explains addition, subtraction, multiplication, unary plus and minus, and when arithmetic on size_t gives a representable mathematical result.
- Binary arithmetic and grouping: Describe what binary +, -, and * compute and how parentheses can group sub-expressions to enforce a preferred binding.
Why it matters: These meanings and grouping rules let you read what an arithmetic expression computes. - Unary plus and minus: Distinguish the unary variants of + and - from their two-operand uses: unary minus gives the negative of its operand, while unary plus gives its value.
Why it matters: The same symbols have different meanings when used with one operand rather than two. - Representable size_t arithmetic: Explain the guarantee for +, -, and * on size_t: their result is mathematically correct when it falls within [0, SIZE_MAX], and a result outside that range is arithmetic overflow.
Why it matters: It separates the guarantee of a well-defined unsigned computation from the condition for getting the mathematical result.
Key terms: arithmetic operators, sub-expressions, parentheses, binding, unary variants, negation, unsigned arithmetic, size_t, SIZE_MAX, representable
4.2.2 Division and remainder
Explains how division and remainder work for unsigned values, why their second operand cannot be zero, and how unsigned arithmetic wraps around.
- Division and remainder relationship: Explain what unsigned division and remainder compute and how their results reconstruct the original value.
Why it matters: The relationship lets you interpret or check a division-and-remainder calculation. - Division and remainder limits: State the restriction on the second operand of unsigned division and remainder, and explain why their results cannot overflow.
Why it matters: It distinguishes a forbidden divisor from the overflow behavior of other unsigned arithmetic. - Unsigned arithmetic wraparound: Explain how an out-of-range unsigned result is reduced and apply that rule to size_t at the ends of its range.
Why it matters: Wraparound explains surprising unsigned results at the top and bottom of a type’s range.
Key terms: division, remainder, /, %, unsigned /, unsigned %, overflow, unsigned arithmetic, wraps around, size_t
4.3 Operators that modify objects
This section explains how assignment, compound assignment, and increment and decrement operators modify objects, and why side effects in expressions should be kept simple.
- Assignment and compound assignment: Explain that assignment places a value into an object, while compound assignment combines an arithmetic operation with assignment. State that an assignment operator yields the object's value after modification, not the object itself.
Why it matters: This lets you read an assignment as both a change to an object and an expression with a result. - Prefix and postfix results: Distinguish prefix from postfix increment and decrement by the value each provides to a surrounding expression, while noting that both forms modify the object in the same way.
Why it matters: The distinction matters when an increment or decrement is used as part of a larger expression. - Side effects and expression clarity: Explain why the section discourages modifying objects within complicated value expressions and recommends modifying no more than one object in a statement.
Why it matters: Keeping modifications separate makes the code easier to understand.
Key terms: assignment, assignment operators, object, value, increment operator, decrement operator, prefix increment, postfix increment, postfix decrement, side effects
4.4 Boolean context
Introduces Boolean-context operators as condition-dependent expressions that yield 0 or 1 and groups them into comparisons and logical evaluation.
- Boolean operator results and categories: Explain that several operators yield 0 or 1 depending on whether a condition is verified, and identify comparisons and logical evaluation as the two categories.
Why it matters: This gives you a way to recognize and group the operators discussed next.
Key terms: comparisons, logical evaluation
4.4.1 Comparison
This section explains what C comparison operators return and shows how their results can be used as numbers, including as array indexes.
- Comparison results and boundaries: Distinguish strict comparisons from inclusive comparisons and explain that comparison operators produce false or true.
Why it matters: A comparison produces a value you can use beyond deciding which statement runs. - Numeric uses of comparisons: Explain how the false and true results of comparisons can be used in arithmetic or array indexing, including the section’s sign-array example.
Why it matters: It explains why a comparison can take part in a calculation or select an array element.
Key terms: comparison operators, strict comparisons, false, true, array indexing, sign[false], sign[true]
4.4.2 Logic
This section explains C’s logical operators, their 0-or-1 results, and how short-circuit evaluation can skip an unnecessary operand.
- Logical operators and truth values: Explain what !, &&, and || mean and that their results are the truth values 0 or 1. Describe how applying ! twice turns a value into a truth value that can be used as an array index.
Why it matters: It explains both what a logical expression produces and why its result can select one of two array elements. - Short-circuit operand evaluation: Explain that && and || omit evaluation of their second operand when the first already determines the result. Describe why this can prevent the example’s division by zero.
Why it matters: An operand that is skipped cannot perform an unsafe operation such as division by zero.
Key terms: !, &&, ||, truth values, !!largeA[i], short-circuit evaluation
4.5 The ternary or conditional operator
This section explains how the conditional operator chooses a value while evaluating only the needed branch, and briefly illustrates conditional compilation.
- Conditional expression branch selection: Explain how the ternary operator differs from an if statement and why its unchosen branch is not evaluated. Describe how this keeps the section’s sqrt_real example from passing a negative argument to sqrt.
Why it matters: Choosing a branch within an expression can also prevent an unsafe computation from running. - Conditional compilation error guard: Describe when the #ifdef construct in the example allows the #error condition to be reached.
Why it matters: The example uses a separate, compile-time condition to guard its error directive.
Key terms: ternary operator, conditional operator, expression, operands, conditional compilation, preprocessor directives, #ifdef, #error, __STDC_NO_COMPLEX__
4.6 Evaluation order
This section explains which C operators guarantee operand evaluation order, why most expressions and function arguments do not, and how to avoid depending on an unspecified order.
- Guaranteed conditional evaluation order: Explain that &&, ||, and ?: evaluate their first operand first because it determines whether other operands are evaluated.
Why it matters: This lets you distinguish the operators that guarantee an order from those that do not. - Comma operator semantics and trap: Describe how the comma operator evaluates its operands in order and yields the right operand’s value; distinguish it from commas used for other syntax and explain why A[i, j] results in A[j].
Why it matters: Recognizing the comma operator prevents mistaken assumptions about both indexing and evaluation order. - Unsequenced expressions and side effects: Explain that most operators do not specify the order of their operands and function calls do not specify the order of argument expressions. Justify avoiding side effects in function calls within expressions when the result might otherwise depend on that order.
Why it matters: Avoiding order-dependent side effects keeps an expression’s outcome from changing with compiler choices or surrounding code.
Key terms: &&, ||, ?:, evaluation order, comma operator, ,, A[i, j], side effects, function arguments, sequence
4.7 Summary
Where this is used later
- Basic types (chapter 5) · The discussion of basic types builds on the idea that a type has a range of values.
- The abstract state machine (chapter 5) · Assumes familiarity with evaluating operands and operators.
- Specifying values (chapter 5) · Assumes familiarity with expressions containing operands and operators.
- Bit-precise integer types (chapter 5) · Addition as an arithmetic operation.
- Integer arithmetic (chapter 8) · Ordinary addition, subtraction, and multiplication.
- Unsigned integers (chapter 5) · Provides context for the later treatment of unsigned integers.
- Recursion (chapter 7) · The remainder operation supplies the reduced argument.
- Bit sets and bitwise operators (chapter 5) · Operators that modify an object.
Concepts explained in this chapter
- side effectA side effect is an observable change to program state caused by evaluating an expression or executing an operation.
- operatorAn operator is a C symbol or symbol sequence that specifies an operation on one or more operands.
- set unionSet union is the operation producing a set containing every element that belongs to either operand set.
- unsigned integerunsigned int is a C unsigned integer type that represents nonnegative whole numbers within an implementation-defined range.
- comparisonA comparison operator is a C operator that evaluates a relational condition between two operands and produces a Boolean result.
- conditional logicAn if-else statement conditionally selects between alternative statements based on a controlling expression.
- truthinessA truth value is the logical interpretation of a C value as true or false in a condition or logical operation.
These guides are generated from the text of Manning books by Manning's knowledge graph and link to the sections they draw on. The full books are available on liveBook and at manning.com.
