Conditions and selected branches
Which branch changes bonus?
The program below starts with score=12. Does it give the bonus for the first satisfied condition, or keep looking for a larger bonus? Predict both printed numbers before reading the trace.
#include <stdio.h>
int main(void)
{
int score = 12;
int bonus = 0;
if (score >= 10) {
bonus = 2;
} else if (score >= 12) {
bonus = 5;
} else {
bonus = 1;
}
printf("%d %d\n", bonus, score + bonus);
return 0;
}You already know how an assignment changes the current state. A condition now decides whether an assignment is reached. This lesson uses C11 and small signed int values; every value read is initialized. All evaluated arithmetic stays within −32767…32767. Each displayed complete program can be compiled separately. The original practice question 3 is deliberately unsafe and must only be read, not run.
A condition has a value
A condition is an expression used to decide what happens next. With the integers in this lesson, zero means false and every nonzero value means true. Thus 0 is false, while 5 and −3 are both true. A negative number is not automatically false. Testing a variable does not change its stored value: if x contains −3, testing x does not replace x with 1.
A comparison computes an int result: 1 when its relation holds and 0 otherwise. Use < and > for strict comparisons, <= and >= to include equality, == to test equality, and != to test inequality. For score=12, score>=10 gives 1, score==10 gives 0, and score!=10 gives 1. These tests read score; none assigns a new score.
Keep assignment and equality separate. x = 5 stores 5 in x. x == 5 asks whether the current x is 5. An assignment is permitted in a C condition, but it still assigns: if the condition is x = 5, x becomes 5 and the condition is true. That is not an equality test. Write x == 5 when equality is intended, and keep ordinary updates in their own statements as in the earlier lessons.
Source rule: comparison results and integer conditions are specified in C11 draft N1570, §§6.5.8p6, 6.5.9p3 and 6.8.4.1p2; assignment value is covered by §6.5.16p3.
Choose one route with if, else if and else
Read the program in worked example 1 in this order. The parentheses contain each condition. The braces contain the statements belonging to that branch.
- Test score>=10. If it is true, run that first braced body and skip the entire else part.
- Only if the first test is false, reach the second if inside the else part. Test score>=12 then.
- Only if both reached tests are false, run the final else body. A final else has no condition of its own.
- Continue with printf after the whole chain.
An else-if chain is one connected choice. It does not rank conditions or choose the largest assigned value. This particular ordering makes the score>=12 branch unreachable: any score that reaches it has already failed score>=10 and therefore is below 10. If the intended rule were to prefer the higher threshold, test score>=12 first. We trace the program actually written before deciding whether its rule is the intended one.
Worked example 1: first match wins
- After the declarations: score=12, bonus=0.
- First test: 12>=10 gives 1, so enter the first body.
- Execute bonus=2. State: score=12, bonus=2.
- Skip the entire else part. The expression score>=12 is not evaluated on this run, and neither later assignment is executed.
- printf reads bonus=2 and computes score+bonus as 12+2=14. Output: 2 14, followed by a newline.
The output is not 5 17: a later mathematically true condition is irrelevant when execution never reaches its test. It is not 1 13 either: else is the alternative to a false condition, not a statement that always runs last.
By contrast, two separate if statements are two decisions. After the first one finishes, execution reaches the second one and evaluates it using the current state. Both bodies may run. If the first body changes a variable used by the second test, use the new value there. Practice question 1 below makes this distinction concrete without changing its tested variable.
Build a condition with !, && and ||
Logical operators also produce the int value 0 or 1. They do not return an arbitrary nonzero operand. For example, the logical expression (-3) && 5 is true and produces 1, not 5 or -3.
- ! means logical NOT. !0 is 1, while !5 and !(-3) are 0. It reverses truth, not the sign of a number.
- With left && right, evaluate the left operand first. If it is zero, the whole result is 0 and do not evaluate the right operand. If left is nonzero, evaluate right; the result is 1 exactly when right is also nonzero.
- With left || right, evaluate the left operand first. If it is nonzero, the whole result is 1 and do not evaluate the right operand. If left is zero, evaluate right; the result is 1 exactly when right is nonzero.
The skipped work is called short-circuit evaluation. “Skipped” is stronger than “calculated but ignored”: the expression is not evaluated at all. For && and ||, the first operand is sequenced before the second when the second is needed. This sequencing rule belongs to these logical operators; it is not a rule that every C expression evaluates from left to right. See N1570, §6.5.3.3p5 and §§6.5.13p3–4, 6.5.14p3–4.
Worked example 2: prove which divisions are skipped
#include <stdio.h>
int main(void)
{
int divisor = 0;
int value = 15;
int all = (divisor != 0) && (value / divisor > 2);
int any = (value > 10) || (value / divisor > 2);
printf("%d %d\n", all, any);
return 0;
}- Initial state: divisor=0, value=15.
- To initialize all, first evaluate divisor!=0. The comparison 0!=0 gives 0.
- The left operand of && is false, so value/divisor>2 is skipped. No division occurs. Store all=0.
- To initialize any, first evaluate value>10. The comparison 15>10 gives 1.
- The left operand of || is true, so value/divisor>2 is skipped here too. Store any=1.
- printf prints 0 1, followed by a newline. divisor and value still contain 0 and 15.
The zero stored in divisor does not itself make this run undefined: no evaluated operation divides by it. Reversing either pair of operands would make the division occur first for these initial values and would lose that protection. The && guard divisor!=0 also protects the division when the starting divisor changes: if zero it skips, and if nonzero the quotient with numerator 15 is representable. The || expression shown is safe for value=15 because its left operand is true; it is not a general zero-divisor guard. For value=3 and divisor=0, that particular || left operand would be false and its right operand would divide by zero. A true result in one run is not a safety proof for all inputs.
Test an inclusive interval with two comparisons
To ask whether m is from 2 through 5 including both ends, require both facts: m>=2 and m<=5. Write (m>=2)&&(m<=5). At m=2 both comparisons hold; at m=5 both hold; at m=1 the first fails, and at m=6 the second fails. Use parentheses to make the two comparisons easy to read.
Do not import the mathematical shorthand 2<=m<=5 into C. C groups it as (2<=m)<=5. With m=8, the first comparison gives 1 and then 1<=5 gives 1, incorrectly accepting 8. In fact either first result, 0 or 1, is <=5. Parenthesizing the whole chained expression does not repair its meaning. The correct two-comparison expression must join the conditions with &&. Also, (m>2)&&(m<5) is a different interval because it excludes 2 and 5.
Practice: attempt all four before opening the solutions
The questions use new values or a different control-flow demand from the worked traces. They are unscored practice.
Practice question 1: two independent decisions
Find the final flags value. State whether each body runs and explain why this is different from an else-if chain.
#include <stdio.h>
int main(void)
{
int m=9,flags=0;
if(m%3==0){flags+=1;}
if(m>5){flags+=2;}
printf("%d\n",flags);
return 0;
}Practice question 2: negation followed by AND
Find z. Name the value of !a, say whether the right operand of && is evaluated, and give the logical result.
#include <stdio.h>
int main(void)
{
int a=0,b=5;
int z=(!a)&&(b>2);
printf("%d\n",z);
return 0;
}Practice question 3: repair the guard order
For d=0 and n=14, is the condition below safe? Rewrite only the order of its two operands so that it safely guards division. Do not execute the original to guess an output; this is a behavior-classification and repair question.
#include <stdio.h>
int main(void)
{
int d=0,n=14;
int result=(n/d>1)&&(d!=0);
return 0;
}Practice question 4: keep the endpoints
For a general int m, which expression tests the inclusive interval [2,5]?
- A: (m>=2)&&(m<=5)
- B: (m>2)&&(m<5)
Choose A or B, then predict both printed results in the m=5 check.
#include <stdio.h>
int main(void)
{
int m=5;
printf("%d %d\n",(m>=2)&&(m<=5),(m>2)&&(m<5));
return 0;
}Practice solutions and wrong-answer feedback
Practice question 1 solution
- Start with m=9, flags=0.
- Compute m%3 as 9%3=0; 0==0 gives 1. The first body adds 1, leaving flags=1.
- Reach the second independent if. The test 9>5 gives 1, so its body adds 2 to the current flags=1.
- Final flags=3; output is 3 and a newline. Both tests were evaluated and both bodies ran.
A result of 1 wrongly skips the second if as though it were an else-if. A result of 2 keeps only the second change, but += adds to the current flags instead of replacing it. A result of 0 overlooks that both comparisons are true. m remains 9 throughout.
Practice question 2 solution
- a=0 and b=5.
- !a is !0, which gives 1.
- Since the left operand of && is nonzero, evaluate b>2. The comparison 5>2 gives 1.
- Both operands are true, so z=1 and the output is 1 followed by a newline.
The answer 0 confuses !0 with 0. The answer 5 confuses a logical result with b's stored value: && yields 0 or 1. Neither a nor b changes, and the right operand is not skipped on this run.
Practice question 3 solution
The original has undefined behavior. Its left operand evaluates n/d before there is any check of d; at these values that is 14/0. Under C11 there is no prescribed output, and a crash is not a guaranteed outcome. The fact that the right comparison would be false cannot undo the already evaluated invalid division.
The repaired condition is (d!=0)&&(n/d>1). A complete safe check is:
#include <stdio.h>
int main(void)
{
int d=0,n=14;
int result=(d!=0)&&(n/d>1);
printf("%d\n",result);
return 0;
}- d!=0 evaluates as 0!=0 and yields 0.
- && skips n/d>1, so no division occurs.
- result becomes 0; this repaired program prints 0 and a newline.
Merely adding parentheses around the original condition does not change operand order. Replacing && with || does not repair the original leftmost division. “The original returns 0 because d is zero” is also wrong: the guard is too late. The unsafe classification follows from N1570, §6.5.5p5; the repair uses §6.5.13p4.
Practice question 4 solution
A is correct. At m=5, the first expression checks 5>=2 and 5<=5, both true, and produces 1. The second checks 5>2 (true), then 5<5 (false), and produces 0. The printed pair is 1 0, followed by a newline.
B would also reject m=2, so it cannot describe an interval that includes both endpoints. The pair 1 1 misses the strict upper comparison; the pair 0 0 wrongly treats equality as disallowed by <=. For a lower boundary check, m=1 makes A's first comparison false, so its right comparison is skipped; that still correctly gives 0.
Carry the state forward
Before moving on, explain worked example 1's skipped second test and worked example 2's skipped divisions without evaluating them. A branch chooses whether to run a body once. The next lesson repeatedly tests a condition so that a body can run zero, one or several times, with a new state on each pass.
Analogy
Imagine a clerk following a written routing card. “If the parcel weighs at least 10 units, place a blue sticker; otherwise, check the next rule.” Once the first route is taken, the clerk does not also follow the alternative route. Two separate cards saying “if” would instead require two decisions, so both actions could happen. That distinction mirrors the first-match chain and the independent if statements.
A guard is like “Is there a nonzero number of boxes? If so, calculate items per box.” Asking whether the boxes exist must come before division. The comparison does not secretly calculate the quotient. This picture helps with &&, but the actual C rule is exact: a zero left operand skips the right operand. For ||, a nonzero left operand skips the right operand. The analogy does not permit reordering the checks.
Quick reference
- An int condition is false at 0 and true at every nonzero value, including negative values.
- <, >, <=, >=, == and != produce int 0 or 1. x=5 assigns; x==5 compares.
- !0 is 1; ! of a nonzero value is 0.
- left && right: evaluate left first; skip right when left is zero.
- left || right: evaluate left first; skip right when left is nonzero.
- && and || produce 0 or 1. Their sequencing is not a general left-to-right rule for all operators.
- In an if/else-if/else chain, take the first reached true branch and skip its alternatives. Separate if statements are separate decisions.
- Inclusive [2,5]: (m>=2)&&(m<=5). The chained form 2<=m<=5 does not express that interval in C.
- Before dividing by d, put a protecting d!=0 test on the left of &&. A skipped division is not evaluated; an already evaluated division by zero cannot be repaired by a later test.
- To trace: record the current state, each reached condition, the selected body, the skipped operands or branches, and the resulting state.
Notes for this lesson
Sign in to keep your progress. Sign in