Program verification · 1969 · C. A. R. Hoare

An Axiomatic Basis for Computer Programming

Connect preconditions, commands, and postconditions with compositional proof rules so program correctness can be derived rather than guessed from tests.

How to read the original

  1. Read Computer Arithmetic before using familiar algebra on machine values.
  2. Study the assignment and composition rules, then reconstruct the invariant obligations for a loop.
  3. Separate partial correctness from an additional termination argument and identify the assumptions of the chosen language model.

A contract is a statement about executions

Hoare develops axioms and inference rules for reasoning about programs. The article also emphasizes that computer arithmetic needs an explicit model, including overflow behavior. In modern notation, a partial-correctness triple {P} C {Q} says that if command C starts in a state satisfying P and terminates normally, its final state satisfies Q. It does not by itself guarantee termination or describe every possible exceptional outcome.

Use an original example that computes the sum of the first n nonnegative-indexed integers. The program initializes i=0 and s=0, then repeatedly executes s:=s+i and i:=i+1 while i<n. The intended result is s=n*(n-1)/2 for integer n>=0. Assume mathematical integers and sequential execution with no hidden mutation of n.

Work backward through one assignment

For assignment x:=E, a sufficient precondition for postcondition Q is Q with E substituted for the occurrences of x that the assignment changes. To guarantee s+i equals a target T after s:=s+i, the prior state must satisfy old_s+i=T. The rule does not mean replacing every variable indiscriminately, and aliasing or side effects in expressions require a richer language model.

For the loop, propose invariant I: 0<=i<=n and s=i*(i-1)/2. Initialization establishes I because both i and s are zero. Assume I and the guard i<n hold before the body. After adding i, the new sum is i*(i-1)/2+i=i*(i+1)/2. After incrementing i, this is exactly new_i*(new_i-1)/2. The bound new_i<=n follows from old_i<n for integer indices.

Separate the three proof obligations

Initialization establishes the invariant before the first test. Preservation establishes it after every complete iteration. Exit combines I with NOT(i<n), yielding i=n and therefore the desired sum. These are distinct obligations; merely writing down an invariant is not a proof.

Trace n=4 to make the algebra concrete. The loop-header states (i,s) are (0,0), (1,0), (2,1), (3,3), and (4,6). Every state satisfies the invariant. An incorrect update order that increments i before adding it would instead sum one through n. A proof attempt exposes the changed algebra even if a superficial test happens to overlook the error.

Termination requires an additional measure. The nonnegative integer n-i decreases by one on each iteration while the guard holds. It cannot decrease indefinitely, so the loop terminates. This converts the partial-correctness argument into a total-correctness argument under the stated domain and arithmetic assumptions.

The model is part of the claim

If s is an eight-bit unsigned integer, the mathematical invariant can fail through wraparound. For n=24 the true sum is 276, which exceeds 255. A proof for that implementation must restrict n, use a wider representation, or specify a modular postcondition. A verifier that reasons about unbounded integers while the program executes wrapping arithmetic proves the wrong model.

Specifications can also be wrong. Proving that a function returns a sorted sequence does not prove it preserves the original elements unless the postcondition includes that property. Testing and proof are complementary: tests expose mismatches in concrete implementations and environments, while formal reasoning establishes quantified claims within explicit semantics. The value of the method lies in making those obligations inspectable rather than treating a successful run as universal evidence.

Reconstruction exercise

Prove a loop that counts occurrences of a target in a finite immutable sequence. Use index i and counter c, give a prefix-count invariant, and show initialization, preservation for both matching and nonmatching elements, exit, and termination. Then identify the weakest useful precondition for x:=x+3 to establish x>=10 under mathematical-integer arithmetic. Explain how an eight-bit wrapping implementation changes that assignment argument, and give one input that refutes blindly reusing the unbounded-integer precondition.

Worked solution

At the loop header, require 0<=i<=n and c equal to the number of target occurrences in positions zero through i-1. Initialization uses i=c=0 and the empty prefix. On a matching element, increment c; otherwise leave it unchanged. Incrementing i then extends the counted prefix by exactly that element, preserving the invariant in both cases. At exit i=n, so c counts the whole sequence. The decreasing measure n-i proves termination because the sequence is finite and unchanged. For x:=x+3 over mathematical integers, substitution gives x+3>=10, equivalent to x>=7. With unsigned eight-bit wrapping, x=254 satisfies x>=7 but becomes one, violating the postcondition. A correct precondition for that representation must exclude wrapping values as well as ensure a large enough result, for example 7<=x<=252.

Test the limits and transfer the idea

1. Program proof · allow 50 minutes

Complete the proof obligation for division

Using unbounded mathematical integers, prove this repeated-subtraction program returns quotient q and remainder r for inputs a ≥ 0 and b > 0:

r := a
q := 0
while r >= b:
    r := r - b
    q := q + 1

Give an invariant, demonstrate preservation across the two sequential assignments, and prove termination separately. Trace (a,b)=(17,5), including the transient state between assignments. Explain what happens with b=0 and why a partial-correctness loop rule alone does not rule it out. This is a modern proof exercise closely related to the paper's division example.

Get a starting hint

At the loop header use a = bq + r and r ≥ 0. Between the two assignments the same header invariant need not yet hold; derive the assertion that connects them.

Compare your reasoning with the worked solution

At initialization, q=0 and r=a, so a=bq+r and r≥0. Suppose the invariant and guard r≥b hold. After r := r-b, the intermediate assertion is a=bq+r+b, with the new r nonnegative. After q := q+1, that assertion becomes a=bq+r again because the new q absorbs the extra b. This explicitly respects sequential assignment rather than pretending both variables changed simultaneously.

At termination the false guard gives r<b, so together with the invariant we have a=bq+r and 0≤r<b. These characterize quotient and remainder for positive b. For termination, r is a nonnegative integer at each header and decreases by the positive amount b on every iteration. It cannot decrease indefinitely while remaining nonnegative.

For (17,5), header states (q,r) are (0,17), (1,12), (2,7), and (3,2). On the first subtraction, the transient state is (0,12), satisfying 17=5×0+12+5; the following increment restores the header equation. The answer is quotient 3 and remainder 2.

If b=0, the guard remains true, r never decreases, and q increases forever. The equation a=bq+r remains true throughout, so invariant preservation alone does not establish termination. Hoare explicitly distinguishes conditional correctness from successful termination. The proof also assumes mathematical integers and side-effect-free expressions; a bounded implementation must justify overflow behavior and resource limits separately.

Check your reasoning

  • Proves initialization, the intermediate assertion, preservation, and the exit condition.
  • Uses r as a decreasing natural-number variant under b>0.
  • Shows b=0 can preserve the invariant while failing to terminate.

2. Weakest precondition and exhaustive check · allow 40 minutes

Find the missing arithmetic precondition

Consider if x < 0: x := -x and postcondition x ≥ 0. First interpret x as an unbounded integer. Then interpret it as a signed eight-bit two's-complement value, with every arithmetic result wrapped modulo 256 and decoded into [-128,127].

Derive the weakest precondition for the postcondition under each model. Identify every failing machine input by exhaustive enumeration. Finally, discuss the stronger specification output = mathematical absolute value of the original input. Why must a proof name its arithmetic model rather than merely display the source code?

Get a starting hint

Negating the minimum signed value produces a bit pattern that still represents the minimum signed value. Define the wrap function explicitly before testing.

Compare your reasoning with the worked solution

Over mathematical integers the weakest precondition is true: nonnegative inputs are left unchanged, and negating a negative integer gives a positive integer. In the eight-bit wrapping model, every input except -128 satisfies the postcondition. Negating -128 gives mathematical 128, whose wrapped signed interpretation is -128 again. Thus the weakest precondition is x ≠ -128 within the declared input domain.

def wrap8(n):
    return (n + 128) % 256 - 128
def abs8(x):
    return wrap8(-x) if x < 0 else x
failures = [x for x in range(-128,128) if abs8(x) < 0]
assert failures == [-128]
assert all(abs8(x) == abs(x)
           for x in range(-127,128))

For the stronger specification, all allowed inputs except -128 again succeed. The mathematical absolute value of -128 is 128, which cannot be represented in the output type. Satisfying that specification for every input requires widening the output representation or changing the interface to report an exceptional case. A different language may trap or have undefined signed-overflow behavior rather than wrap, so this model cannot be silently applied to it.

The finite enumeration verifies every input in this particular machine domain, while the reasoning identifies why the exceptional value exists. Hoare's axiomatic method requires the implementation to satisfy the arithmetic axioms used by the proof. Identical-looking source text can denote different state transformations under different numeric semantics; a proof of one transformation does not automatically prove another.

Check your reasoning

  • Derives true over integers and x≠-128 over signed wrapping eight-bit values.
  • Enumerates exactly one failing input and explains representation limits.
  • Distinguishes wrapping, trapping, and other language semantics.

Check your understanding

A partial-correctness triple is valid, but its loop never terminates on one allowed input. Is that automatically a contradiction?

  • Yes, every Hoare triple guarantees termination.
  • No, partial correctness constrains terminating executions; termination needs a separate obligation.
  • Only if the loop contains addition.
Explanation

Partial correctness promises the postcondition only if execution terminates normally. Total correctness additionally requires a termination argument.

For mathematical integers, what precondition makes x:=x+3 establish x>=10?

  • x>=7.
  • x>=10 only.
  • x=13.
Explanation

Backward substitution yields x+3>=10, whose weakest solution is x>=7.

Original paper and sources

C. A. R. Hoare. An Axiomatic Basis for Computer Programming. Communications of the ACM 12(10), October 1969, pp. 576-580 and 583. Linked resource is a university-hosted copy of the article.

Read the original paper →

Read the primary paper: University of California, Santa Barbara