Skip to content

Expression values and safe updates

Lesson 2 of 1313 minPDF notesFree

What happens when an integer is negative?

C
#include <stdio.h>

int main(void)
{
    int a = -19;
    int b = 4;
    int q = a / b;
    int r = a % b;
    int z = 2 + q * 3;
    printf("%d %d %d\n", q, r, z);
    return 0;
}

The quotient is not obtained by always rounding downward, and the remainder need not be positive. To trace this program, we need the exact integer rule. Later in this lesson we will keep an expression's value separate from any change it makes to a variable.

Carry forward the tracing rules

You need the previous lesson's current-state method, initialization, simple assignment, arithmetic grouping, and %d output. Shorter code blocks below are separate bodies for the same complete wrapper shown above: #include <stdio.h>, int main(void), braces, and a final return 0;. Start each block with its own initial state.

We continue to use ISO C11, initialized signed int values, and intermediate results within -32767 to 32767. Every evaluated divisor is nonzero and each quotient fits in int. We will label one unsafe expression for recognition only; it is not an output-prediction program to run.

Integer division: move toward zero

For integer operands, / discards the fractional part of the mathematical quotient. “Toward zero” tells us which integer remains on the negative side. The mathematical quotient of -19 by 4 is -4.75; discarding its fractional part gives -4, which is nearer zero. It does not give -5.

Use three steps rather than guessing the remainder's sign:

  1. Find the integer quotient q by truncating toward zero.
  2. Calculate the remainder using r = a - q * b.
  3. Check that q * b + r reconstructs a.

These arithmetic equalities describe the values; an equality written here is not a C assignment statement to paste into the program. For this lesson's bounded operands, the multiplication and subtraction in the check also fit safely in int.

The remainder is zero or has the sign of the dividend a. Its magnitude is smaller than the magnitude of the divisor b. The divisor's sign affects the quotient, but it does not determine the sign of a nonzero remainder. This is C remainder; do not replace it with a convention from another language that always gives a nonnegative result for a positive divisor.

Worked example 1: quotient, remainder, then an expression

Trace the opening program from the declarations.

  1. After the first two declarations: a=-19, b=4.
  2. q = a / b yields -4, because the fractional part of -4.75 is discarded toward zero. Now q=-4.
  3. r = a % b gives -3. Check: (-4) * 4 = -16, and -19 - (-16) = -3. Now r=-3.
  4. For z = 2 + q * 3, multiplication binds more tightly: q * 3 = -12, then 2 + (-12) = -10. Now z=-10.
  5. The output is -4 -3 -10 followed by a newline, in the order q, r, z. Neither a nor b has changed.

The pair -5, 1 reconstructs -19, but it uses downward rounding, so it is not C11's quotient/remainder pair. Reconstruction alone is not sufficient: it must agree with truncation toward zero. The pair -4, 3 uses the right quotient but fails reconstruction: -16 + 3 is -13.

Check the sign rule in three nearby cases

C
printf("%d %d\n", 19 / -4, 19 % -4);
printf("%d %d\n", -19 / -4, -19 % -4);
printf("%d %d\n", -20 / 4, -20 % 4);
  1. 19 / -4 gives -4; 19 - (-4)*(-4) = 3. The first line is -4 3.
  2. -19 / -4 gives 4; -19 - 4*(-4) = -3. The second line is 4 -3.
  3. -20 / 4 gives -5; -20 - (-5)*4 = 0. The third line is -5 0.

A negative dividend can still have remainder zero. “The remainder follows the dividend's sign” needs that zero exception. These calls only read constants, so argument-evaluation order cannot change them.

Grouping equal-priority operations

*, /, and % share a precedence level and group from the left. For example, 18 / 4 * 3 groups as (18 / 4) * 3. The first quotient is 4, then the product is 12. It does not group as 18 / (4 * 3), whose result would be 1.

Discarding a fraction can happen at an intermediate division. You cannot generally do all calculations using fractions and truncate only once at the end: (18 / 4) * 3 in C has already lost the fractional part before the multiplication uses that quotient. Parentheses can change which division is requested. They do not generally create a left-to-right evaluation order for side effects. Our examples in this section have no side effects inside the arithmetic expression.

A value calculation is different from an update

C
int x = 4;
int next = x + 1;
printf("%d %d\n", x, next);

x + 1 produces 5, which initializes next. It does not store anything into x. The output is 4 5. In contrast, x = x + 1; would store the result back into x. Ask two questions whenever an expression might update a variable: “What value does it supply?” and “What is the state when this statement is finished?”

Compound assignments to one variable

For the ordinary int variable used here, score += 5; has the same numerical effect as score = score + 5;. Similarly, score -= 4; subtracts 4 and stores the result, and score /= 5; stores the integer quotient. These operators combine arithmetic with assignment; += is not just a different way to write +.

C
int score = 17;
score += 5;
score -= 4;
score /= 5;
printf("%d\n", score);
  1. Initialization gives score=17.
  2. After score += 5;: score=22.
  3. After score -= 4;: score=18.
  4. After score /= 5;: score=3, since integer 18 / 5 is 3.
  5. The output is 3 followed by a newline.

The compound form evaluates its left-hand destination only once. We limit this lesson to a plain variable, so there are no extra address calculations to track. Do not treat the expanded form as a permission to duplicate more complicated expressions. A compound assignment still needs the same arithmetic checks: no evaluated division by zero, and no out-of-range signed result.

Increment: two questions, not one slogan

The increment operator ++ adds one to a variable. Its position determines the value supplied by the expression:

  • In x++, called postfix increment, the expression supplies the old value of x. The variable is also increased by one
  • In ++x, called prefix increment, the expression supplies the increased value of x

These are not two different amounts of increase: each increases x by one. If the old value is 3, the state after a separate x++; or ++x; statement has x=4. The difference matters when another variable receives the expression's value.

We place each update in its own full expression. An initializer such as the one in int before = x++; is a full expression; so is the expression in a standalone update statement. Its required updates are complete before the next full expression begins. Postfix does not mean “wait until some later statement to update”. Do not turn several separate statements into a combined expression while assuming their meaning will be preserved.

Worked example 2: the supplied value and the final state

C
int x = 3;
int before = x++;
int after = ++x;
printf("%d %d %d\n", before, after, x);
  1. After int x = 3;: x=3.
  2. In int before = x++;, the postfix expression supplies 3 to initialize before. By the end of this declaration, x has increased to 4. State: x=4, before=3.
  3. In int after = ++x;, x increases from 4 to 5, and the prefix expression supplies 5 to initialize after. State: x=5, before=3, after=5.
  4. The output is 3 5 5 followed by a newline, because the arguments are before, after, then x.

before stays 3; it is a stored copy of a value, not another name for x. If you got 3 4 5, you gave the prefix expression an old value. If you got 4 5 5, you gave the postfix expression a new value. Record the supplied value and resulting state separately to avoid both mistakes.

A precise safety boundary

C does not promise to evaluate the operands of ordinary + from left to right. When an expression changes a variable and also reads that variable elsewhere without the required sequencing, it can have undefined behavior. Undefined behavior means C11 gives no prescribed result for the program; it is not merely a choice between two normal answers, and a crash is not required.

Keep the updates in separate full expressions as in worked example 2. Parentheses that change arithmetic grouping do not, by themselves, establish sequencing between operands. A single compiler run is not a rule for an unsafe expression. Practice question 4 asks you to recognize this boundary instead of guessing a number.

Practice: attempt before the solutions

These four original exercises are unscored. For each defined example, write the expression values and final state. For the unsafe example, give a semantic classification instead of a numeric answer.

Practice question 1: negative dividend

Find the C11 quotient and remainder. Give the output and show the reconstruction check.

C
int a=-23,b=6;
printf("%d %d\n",a/b,a%b);

Practice question 2: two different increment forms

Find the final n and the values stored in p and q. Pay attention to the printed argument order.

C
int n=4;
int p=++n;
int q=n++;
printf("%d %d %d\n",n,p,q);

Practice question 3: the effect of parentheses

Find both results and explain why they differ.

C
int a=24/5*2;
int b=24/(5*2);
printf("%d %d\n",a,b);

Practice question 4: classify, do not execute

Does C11 give a defined value for r in this fragment? Explain whether a single numeric output could be required if r were printed afterward. Do not run the fragment to choose a number.

C
int i=4;
int r=i++ + i;

Practice solutions and wrong-turn feedback

Practice question 1 solution

  1. The mathematical quotient of -23 and 6 lies between -4 and -3; truncation toward zero gives q=-3.
  2. q*b = (-3)*6 = -18.
  3. The remainder is a-q*b = -23-(-18) = -5.
  4. Check: (-3)*6+(-5)=-23; the remainder's magnitude 5 is below 6, and its sign agrees with the negative dividend.
  5. Output: -3 -5 followed by a newline.

-4 1 applies floor division instead of C11 truncation. -3 5 incorrectly forces the remainder positive and reconstructs -13, not -23. -3 -1 has a plausible sign but fails the arithmetic check: -18-1=-19.

Practice question 2 solution

  1. Initially n=4.
  2. int p=++n; first increases n to 5 and supplies 5 to p. State: n=5, p=5.
  3. int q=n++; supplies the current 5 to q and increases n to 6 by the end of the declaration. State: n=6, p=5, q=5.
  4. The print order is n, p, q, so the output is 6 5 5 followed by a newline.

6 5 6 gives postfix the new value. 5 5 5 forgets that postfix still changes n. 6 4 5 gives prefix the old value. Both updates have already happened when printf runs.

Practice question 3 solution

  1. In the first declaration, equal-priority / and * group from the left: a=(24/5)*2.
  2. Integer 24/5 is 4; then 4*2=8. Thus a=8.
  3. In the second declaration, parentheses form the divisor 5*2=10. Integer 24/10 is 2. Thus b=2.
  4. Output: 8 2 followed by a newline.

2 2 silently adds parentheses to the first expression. 9 2 uses the fractional intermediate 4.8 and truncates only after multiplying; the C expression already used an integer quotient of 4. 8 8 ignores the parentheses in the second declaration. Grouping and integer division are both needed for the explanation.

Practice question 4 solution

The fragment has undefined behavior under C11. Within i++ + i, the increment's modification of i is unsequenced relative to the other operand's read of i. Ordinary + does not supply the sequencing needed to make this safe. The rule is N1570 §6.5p2.

There is no C11-prescribed numeric value for r, and no single output a conforming trace question may require. 8 and 9 are tempting calculations based on imagined evaluation timings, not valid standard-defined answers. “Either 8 or 9” is also incorrect: undefined behavior does not restrict the implementation to those two outcomes. “It must fail to compile” and “it must crash” are not guaranteed either. Adding parentheses around i++ or around the whole sum does not repair the sequencing.

If the intended operation is “keep the old value, increment, then add old and new”, express those steps separately:

C
int i = 4;
int old = i;
i = i + 1;
int r = old + i;
printf("%d %d\n", r, i);
  1. Initially i=4.
  2. old=i stores 4 in old.
  3. i=i+1 makes i=5.
  4. r=old+i uses 4+5=9.
  5. Output: 9 5 followed by a newline, for r and i.

This is a new, defined program with an explicitly chosen intention. It does not prove that the unsafe fragment “really meant 9”. The unsafe fragment had no defined meaning to preserve.

Before moving on

You can now distinguish a calculation from a stored update, trace prefix and postfix in separate full expressions, and apply C11's integer quotient/remainder rule. Next, conditions will use expression values to decide which statements run.

Source notes

The code, calculations, and feedback are original. Precise references in the WG14 N1570 C11 committee draft: integer division/remainder §6.5.5p5–6; left grouping §6.5.5p1; compound assignment §6.5.16.2p3; postfix §6.5.2.4p2; prefix §6.5.3.1p2; full expressions §6.8p4 and their sequencing §5.1.2.3p3; unsequenced modification/read §6.5p2; out-of-range results §6.5p5. Compiler output may corroborate the defined examples, but it is not the language definition.

Analogy

Imagine a counter and a small receipt. With postfix, the receipt records the old counter reading while the counter advances by one; with prefix, the counter advances and the receipt records the new reading. After the whole transaction, the counter has advanced in either case, but the receipts can differ. Make one transaction complete before starting the next. This picture helps with separate full expressions; it does not define a safe order for several updates squeezed into one C expression.

Quick reference

  • C11 integer / truncates toward zero: -19 / 4 is -4
  • For nonzero b and a representable quotient: (a / b) * b + a % b equals a
  • A nonzero remainder has the dividend's sign; its magnitude is below the divisor's magnitude
  • * / % group from the left: 24 / 5 * 2 means (24 / 5) * 2
  • For a plain int variable, x += k, x -= k, and x /= k store the corresponding arithmetic result
  • x + 1 calculates a value without changing x; x = x + 1 stores it
  • x++ supplies the old value; ++x supplies the new value; both increase x by one
  • Separate full expressions make the shown updates finish before the next begins
  • i++ + i has undefined behavior, not a prescribed numeric answer
  • Parentheses control grouping; they do not generally sequence side effects
  • Keep every intermediate in range and every evaluated divisor nonzero

Notes for this lesson

Sign in to keep your progress. Sign in