C Part I · Module 3 · Expressions and Operators
Logical Operators and Short-Circuit Evaluation
Chapter 3.5 let you ask one question about two values. Real programs ask several at once, and C provides three operators for combining them. Two of those three have a property no operator you have met so far possesses: under the right conditions they do not evaluate one of their operands at all, and they are specified to behave that way. That is not a compiler optimisation you happen to benefit from — it is a guarantee written into the language, which means correct code is allowed to depend on it. This chapter demonstrates the skipping directly rather than asserting it, using nothing more than the assignment you learned in the previous chapter, and finishes by separating these operators from two others that look similar and behave very differently.
Foundation12 min readCPart IFundamentalsExpressions
Part I · Module 3 · Chapter 3.6 · Logical Operators and Short-Circuit Evaluation
1. Combining Questions
A single comparison answers one question. Programs routinely need combinations: is the value in range means both above the minimum and below the maximum.
C has three operators for this:
a && b and — true when both a and b are true
a || b or — true when at least one of a and b is true
!a not — true when a is falseThey work on truth, which Chapter 2.10 defined for C: zero is false, every other value is true. So the operands of these operators do not have to be comparisons — any value will do, and it is judged by that rule.
Their results follow Chapter 3.5's pattern exactly:
&&,||and!each produce anint— 1 or 0.
! in particular is precise about this: it produces 1 if its operand compares equal to zero, and 0 otherwise. So !x on an x holding 42 gives 0, not "the opposite of 42".
2. The Property That Makes Two of Them Different
Every operator so far has evaluated both of its operands. a + b needs both values; a < b needs both values. && and || are the first exceptions, and the rule is:
The left operand is evaluated first. If it already settles the answer, the right operand is not evaluated at all.
For &&, a false left operand settles it — false and anything is false. For ||, a true left operand settles it — true or anything is true.
3. Proving It
An assertion about what does not happen is worth proving. Chapter 3.4 established that assignment is an expression with a side effect — so put an assignment in the right operand and see whether the object changed:
#include <stdio.h>
int main(void)
{
int changed_by_and = 0;
int changed_by_or = 0;
int and_result = (0 && (changed_by_and = 1));
int or_result = (1 || (changed_by_or = 1));
printf("and_result %d, changed_by_and %d\n", and_result, changed_by_and);
printf("or_result %d, changed_by_or %d\n", or_result, changed_by_or);
return 0;
}Output:
and_result 0, changed_by_and 0
or_result 1, changed_by_or 0Both objects still hold 0.
Take the first line apart. The left operand of && is 0, which is false, so the result is already known to be false. The right operand (changed_by_and = 1) was therefore not evaluated — and because it was not evaluated, its side effect never happened and changed_by_and was never assigned.
The second line is the mirror image. The left operand of || is 1, which is true, the answer is settled, and the assignment on the right never ran.
That is as direct as evidence gets: the only way changed_by_and can still be 0 is if the expression that would have set it was skipped.
4. A Guarantee, Not an Optimisation
It is tempting to file this under "the compiler is being clever". It is not, and the distinction matters.
C specifies short-circuit behaviour for && and ||. A conforming implementation must skip the right operand when the left settles the answer. It is not free to evaluate it anyway.
That has a real consequence: correct code may depend on the right operand not being evaluated. The common shape is a check that protects the thing after it — a first operand that establishes something is safe, and a second operand that would be wrong to evaluate otherwise. You will write that pattern constantly from Module 6 onward, and it is only correct because the guarantee exists.
These two operators also do something else unusual, which Chapter 3.11 will make a great deal of:
&&and||impose an order. The left operand is fully evaluated, including its side effects, before the right operand is considered at all.
Almost nothing else in C promises you that. Chapter 3.11 is about what happens when there is no such promise, and these two operators are the reference point it argues against.
5. Not the Same as & and |
C also has operators spelled with a single character — & and | — and they are not these. They are Chapter 3.8's subject and they behave differently in two important ways:
&& and || | & and | | |
|---|---|---|
| What they work on | truth values | individual bits |
| Result | int 1 or 0 | a bit pattern |
| Right operand | may be skipped | always evaluated |
Writing & where you meant && frequently still compiles, because both take integer operands, and it can even give the right answer for particular values — which is precisely what makes it a durable bug. If you mean and in the sense of combining two questions, type two characters.
6. A Realistic Use
Range checking is the shape you will write most often:
#include <stdio.h>
int main(void)
{
int reading = 42;
int minimum = 0;
int maximum = 100;
int in_range = (reading >= minimum) && (reading <= maximum);
int out_of_range = !in_range;
printf("in_range %d, out_of_range %d\n", in_range, out_of_range);
return 0;
}Output:
in_range 1, out_of_range 0Two comparisons, each producing 1 or 0, combined with && into a single answer, then inverted with !. The parentheses around each comparison are not required — Chapter 3.10 explains why — but they cost nothing and make the grouping obvious to a reader.
7. What You Should Be Able to Say Now
&&,||and!combine and invert truth values, using C's rule that zero is false and anything else is true.- Each produces an
int— 1 or 0.!xgives 1 whenxcompares equal to zero. - The left operand is evaluated first.
- If the left operand settles the answer, the right one is not evaluated — false for
&&, true for||. - Skipping means side effects in the right operand do not happen, which is directly observable.
- This is a guarantee in the language, not a compiler optimisation, so correct code may depend on it.
- These two operators also impose an ordering, which almost nothing else in C does.
&and|are different operators that work on bits and always evaluate both operands.- Order your conditions deliberately — especially when the left one is what makes the right one safe.
8. Exercise
Exercise 1 — predict, then run. Give the exact output:
printf("%d\n", 1 && 1);
printf("%d\n", 1 && 0);
printf("%d\n", 0 || 0);
printf("%d\n", !5);
printf("%d\n", !0);Exercise 2 — predict the side effect. For each, say what is printed and what the object holds afterwards:
int n = 0;
int r = (1 && (n = 9));
printf("%d %d\n", r, n);int n = 0;
int r = (0 && (n = 9));
printf("%d %d\n", r, n);Exercise 3 — explain the evidence. In §3, changed_by_and was still 0 at the end. Explain in two sentences why that is proof of short-circuiting rather than merely consistent with it.
Exercise 4 — say why it matters. Explain the difference between "the compiler may skip the right operand" and "the compiler must skip the right operand", and give one consequence for how you may write code.
Exercise 5 — find the wrong operator. A colleague writes a & b intending to combine two comparisons. Give one reason it may appear to work and one reason it is still wrong, using §5.
Exercise 6 — order them. A check needs two conditions: one that is quick and usually settles the matter, and one that is slow. Say which goes on the left of && and why, then say what would change if the slow condition were only valid to evaluate when the quick one held.
9. Summary
&&,||and!combine and invert truth values, judged by C's zero/non-zero rule.- Each produces an
int, 1 or 0 — the same as comparisons. - The left operand goes first, and if it settles the answer the right one is never evaluated.
- Skipping is observable: put an assignment on the right and the object stays unchanged.
- It is a guarantee, not an optimisation. Code may correctly depend on the right operand not running.
- These operators impose an order that almost nothing else in C does — which is Chapter 3.11's reference point.
&and|are different operators. They work on bits and always evaluate both operands.- Condition order is a decision, and sometimes a correctness one.
Two chapters have now leaned on assignment's side effect to demonstrate something. The next chapter introduces two operators whose entire reason for existing is a side effect — and whose two forms differ not in what they do to the object, but in what value the expression hands back.
Standards & specifications
- Governing standard
- ISO/IEC 9899 (the C language standard)(opens ISO/IEC (JTC 1/SC 22/WG 14) in a new tab)
Defines the C language itself — the type system, expression semantics, object model, storage duration and the behaviour categories a conforming implementation must honour. It is a FAMILY of editions (C89/C90, C99, C11, C17, C23) and this curriculum names the edition wherever a claim depends on one. It does not define any foreign-function interface to a hardware description language, and it does not define platform behaviour: what a memory-mapped access does on real silicon is the implementation's and the device's contract, not this standard's. Working drafts published by WG14 are freely available and are what most engineers read day to day; a draft is not the published standard.
This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.
Where this fits
Part of the C Programming curriculum.
