Program Proofs of Correctness

The basic idea here is that a program is a mathematical object, therefore it ought to be possible to prove things about its properties. It follows that if we can precisely say what it is we want the program to do, we can formally prove whether it works or not.

This problem has been studied by a variety of people, with a variety of goals. The strongest goal is the development of a methodology of programming; some people want to see a program developed in terms of formal statements of its behavior, and to create the program through a series of stepwise refinements, each of which is guaranteed to preserve the program's correctness. A more modest (but still ambitious) goal is to take an existing program, and rigorously demonstrate that it works correctly.

I'm happy with a more modest goal yet: if we have some notion of what the semantics of program statements are, then we can reason informally about the behavior of the program and find bugs. Will can also instrument the program with assertions we belief should hold true at strategic points in the execution of the program, and check them.

Program Semantics

From the point of view of the correctness of the program, we need to have a clear idea of the effect of a program statement. There are three basic program structures we need to consider here: the assignment, the conditional, and the merge.

Assignment

// P(e)
v = e;
// P(v)
What this means is that if there is some predicate P we can say about the expression on the right hand side of an assignment before the assignment takes place, then we can make the same statement about the variable to be assigned after the assignment, and vice versa.

Here's an example:


// i == 2
i = i + 1;
// i == 3	  

Condition

// P
if (C)
    // P, C
else
    // P, ~C

What this means is that if there is some assertion we can make before we execute an if-else, we can know that both that assertion, and the condition on which we decide to branch are true when we take the if-clause, and that the original assertion is true but the condition is false if we take the else-clause.

Here's an example:


// i <= 5
if (j < 7)
    // i <= 5, j < 7
else
    // i <= 5, j >= 7

Merge

if ()
    // P
else
	// Q
// P or Q
What this means is that if we have a place in the code with two paths to get there, the result at the place we end up is one of the two assertions, but we don't know which one.

Loops

The semantics of a loop are derived from the semantics of the conditional and the merge. If we're going to be able to say anything useful about the loop, we have to have a single predicate that will be true on each iteration of the loop: this is called the loop invariant. The semantics are expressed as follows:


// I
while (C)  {
    // I, C
    ...code...
    // I
}
// I, ~B

I is called the loop invariant because it is true for each iteration of the loop.

Extended Example

One of my favorite examples of a proof of correctness appears in an example I use frequently in CS 273: Euclid's Algorithm

Tools

There is an include file (required by ISO C) called . It defines a macro called assert, whose argument is an expression. If we say something in our code like


assert(i <= j);
then, when we reach that line of the program, if the expression is not true, the program will print an error message and terminate. If we define a symbol NDEBUG the assertion isn't evaluated.


Last modified: Wed Nov 7 09:22:18 MST 2001