Expression values and safe updates
What happens when an integer is negative?
#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:
- Find the integer quotient
qby truncating toward zero. - Calculate the remainder using
r = a - q * b. - Check that
q * b + rreconstructsa.
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.
- After the first two declarations:
a=-19, b=4. q = a / byields-4, because the fractional part of-4.75is discarded toward zero. Nowq=-4.r = a % bgives-3. Check:(-4) * 4 = -16, and-19 - (-16) = -3. Nowr=-3.- For
z = 2 + q * 3, multiplication binds more tightly:q * 3 = -12, then2 + (-12) = -10. Nowz=-10. - The output is
-4 -3 -10followed by a newline, in the orderq,r,z. Neitheranorbhas 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
printf("%d %d\n", 19 / -4, 19 % -4);
printf("%d %d\n", -19 / -4, -19 % -4);
printf("%d %d\n", -20 / 4, -20 % 4);19 / -4gives-4;19 - (-4)*(-4) = 3. The first line is-4 3.-19 / -4gives4;-19 - 4*(-4) = -3. The second line is4 -3.-20 / 4gives-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
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 +.
int score = 17;
score += 5;
score -= 4;
score /= 5;
printf("%d\n", score);- Initialization gives
score=17. - After
score += 5;:score=22. - After
score -= 4;:score=18. - After
score /= 5;:score=3, since integer18 / 5is3. - The output is
3followed 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 ofx. The variable is also increased by one - In
++x, called prefix increment, the expression supplies the increased value ofx
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
int x = 3;
int before = x++;
int after = ++x;
printf("%d %d %d\n", before, after, x);- After
int x = 3;:x=3. - In
int before = x++;, the postfix expression supplies3to initializebefore. By the end of this declaration,xhas increased to4. State:x=4, before=3. - In
int after = ++x;,xincreases from4to5, and the prefix expression supplies5to initializeafter. State:x=5, before=3, after=5. - The output is
3 5 5followed by a newline, because the arguments arebefore,after, thenx.
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.
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.
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.
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.
int i=4; int r=i++ + i;
Practice solutions and wrong-turn feedback
Practice question 1 solution
- The mathematical quotient of
-23and6lies between-4and-3; truncation toward zero givesq=-3. q*b = (-3)*6 = -18.- The remainder is
a-q*b = -23-(-18) = -5. - Check:
(-3)*6+(-5)=-23; the remainder's magnitude5is below6, and its sign agrees with the negative dividend. - Output:
-3 -5followed 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
- Initially
n=4. int p=++n;first increasesnto5and supplies5top. State:n=5, p=5.int q=n++;supplies the current5toqand increasesnto6by the end of the declaration. State:n=6, p=5, q=5.- The print order is
n, p, q, so the output is6 5 5followed 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
- In the first declaration, equal-priority
/and*group from the left:a=(24/5)*2. - Integer
24/5is4; then4*2=8. Thusa=8. - In the second declaration, parentheses form the divisor
5*2=10. Integer24/10is2. Thusb=2. - Output:
8 2followed 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:
int i = 4;
int old = i;
i = i + 1;
int r = old + i;
printf("%d %d\n", r, i);- Initially
i=4. old=istores4inold.i=i+1makesi=5.r=old+iuses4+5=9.- Output:
9 5followed by a newline, forrandi.
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