IMPORTANT NOTE: This has been moved to here. The notes below are not up to date, and will soon be deleted.
Invariants¶
A programing invariant is a fact that is true at a particular point in a program. For example, consider this function:
// returns 1 + 2 + ... + n; n must be greater than 0
int sum_to(int n) {
// Invariant: n > 0
int result = 0;
for(int i = 1; i <= n; i++) {
// Invariant: 1 <= i <= n
result += i;
}
// Invariant: result == (1 + 2 + 3 + ... + n)
return result;
}
Three invariants are listed:
- The first invariant is a pre-condition, and asserts that
n
is greater than 0, i.e. thatn > 0
is true. - The second invariant is a loop invariant, and asserts that at the start
of each iteration of the for-loop,
i
is between 1 andn
. - The third invariant is a post-condition, and asserts that, after the
loop is done,
result
is equal to the sum of the numbers from 1 ton
.
Checking Invariants with assert¶
A convenient way to automatically check some assertions is to use the
assert
macro. For example:
// returns 1 + 2 + ... + n; n must be greater than 0
int sum_to(int n) {
assert(n > 0);
int result = 0;
for(int i = 1; i <= n; i++) {
assert(1 <= i && i <= n);
result += i;
}
// Invariant: result == (1 + 2 + 3 + ... + n)
return result;
}
The first two invariant comments have been replaced by calls to the assert
macro. To use assert
, pass it a boolean expression, i.e. and expression
that evaluates to true or false. If the expression is true, then assert
does nothing and the program runs as normal. But if the expression is false,
then assert
will immediately crash the program with an error message
saying on which line number the assertion failed. This is useful for
debugging.
Note that assert(false)
will always crash if it is run, while
assert(true)
will never crash if it runs. An assertion like assert(n >
0)
may, or may not, crash, depending upon the value of n
.
What is a macro?¶
assert
is a macro, and not a function. In C, macro are handled by the
pre-processor, and they work somewhat like functions, except macros don’t
evaluate their arguments. Functions always evaluate their arguments, e.g. the
function call f(1+2)
is the same as f(3)
when f
is a function. In
contrast, if f
were a macro, then f(1+2)
and f(3)
could return
different results.
assert
is a macro because that lets it access the line number on which the
assert
occurs in the source code, so that it can print that information
when it fails. A regular function cannot access the line number it’s on. While
C macros do have their uses, they are generally much harder to use than
functions, and not often needed. So in this course we won’t discuss them
beyond this use of assert
.
Pre-Conditions and Post-conditions¶
Some of the most useful assertions are function pre-conditions and post-conditions. A function pre-condition asserts what must be true before a function is called, and a function post-condition asserts what will be true after the function finishes (assuming the pre-condition was true). Also, we assume that the post-condition mentions all changes, so that values not mentioned in the post-condition are assumed to be unchanged.
Together, a functions pre and post conditions form a specification, i.e. they say what a function should do.
Pre-conditions and post-conditions are typically written as comments before the function, e.g.:
// Pre-condition:
// n > 0
// Post-condition:
// returns 1 + 2 + ... + n
int sum_to(int n) {
assert(n > 0);
int result = 0;
for(int i = 1; i <= n; i++) {
assert(1 <= i && i <= n);
result += i;
}
// Invariant: result == (1 + 2 + 3 + ... + n)
return result;
}
It can be useful to think of pre and post conditions as forming a contract between the function and the code that calls it.
For sum_to
to work properly, it requires that n > 0
be true. If it’s
not, then sum_to
cannot promise to work correctly. The post-condition is
what sum_to
promises will be true when its done, assuming the
pre-condition was true when it was called. The code that calls sum_to
is
responsible for ensuring the pre-condition holds, while sum_to
is
responsible for ensuring the post-condition holds.
Notice that there’s no easy way to use assert
to check the post-condition.
sum_to
is calculating that post-condition. This is typical: it is often
easier to automatically check pre-conditions than post-conditions.
In practice, clear and precise pre and post conditions is a valuable programming technique. The more important or complex a function, the more precise you should make the pre and post conditions.
Loop Invariants¶
A loop invariant is an invariant that occurs at the top of a loop body, e.g.:
int result = 0;
for(int i = 1; i <= n; i++) {
assert(1 <= i && i <= n); // loop invariant
result += i;
}
Loop invariants are often quite helpful. Even if you don’t use assert
they
can help you think about loops.
Some loop invariants are more helpful than others. For example, here’s a useless loop invariant:
int result = 0;
for(int i = 1; i <= n; i++) {
assert(2 == 2); // loop invariant
result += i;
}
2 == 2
is always true, so asserting here doesn’t help.
A slightly more useful loop invariant is this:
int result = 0;
for(int i = 1; i <= n; i++) {
assert(i-1 <= result); // loop invariant
result += i;
}
From examining the loop we can see that the result
is indeed always
greater than, or equal to, i - 1
. That’s because result
starts at 0
and only goes up in positive increments of i
.
Notice that assert(i <= result)
is not a loop invariant. The first time
through the loop i
is 1 and result
is 0, and so i <= result
is
false.
This is also a loop invariant, but it is not as useful as the previous one:
int result = 0;
for(int i = 1; i <= n; i++) {
assert(i-1 <= result + 500); // loop invariant
result += i;
}
The invariant i-1 <= result + 500
is implied by i-1 <= result
, and so,
while it is true, it’s not as useful. In practice it can be tricky to come up
with useful loop invariants.
Questions¶
- What is a programming invariant?
- Explain how C’s
assert
macro works. Why is it a macro, and not a regular C function? - Explain what pre-conditions and post-conditions describe for a function.
- What is a loop invariant? Where does it occur in a loop?