wp-Kalkül – Programmverifikation
Beim Entwickeln von Programmen ist es wahrscheinlich, dass Fehler gemacht werden, und das Programm am Ende nicht immer korrekt abläuft. Es gibt verschiedene Methoden, wie man versuchen kann die Korrektheit eines Programms zu verifizieren.
Die trivialste Möglichkeit ist das Testen mit einer bestimmten Menge von Eingabedaten. Liefert das Programm immer das erwartete Ergebnis, so weiß man, dass für die getesteten Eingabedaten das Programm korrekt abläuft. Man hat jedoch keine allgemeine Aussage über die Korrektheit des Programms.
In diesem Artikel wird auf eine mathematische Möglichkeit eingegangen, wie man beweisen kann, dass ein Programm korrekt ist, und zwar über den wp-Kalkül. Der Artikel wird etwas länger werden, da das Thema nicht so ganz einfach ist, und ich lieber etwas ausführlicher die Sachen herleite.
Zusicherungen (Assertions)
Der wp-Kalkül untersucht die Korrektheit von imperativen Programmen. Wir möchten also gewisse Zusicherungen an unser Programm an bestimmten Stellen im Code machen können. Eine Zusicherung ist ein prädikatenlogischer Ausdruck über die Belegung von Programmvariablen an der Stelle vom Programm, an der die Zusicherung erfolgen soll.
Eine Zusicherung vor dem zu untersuchenden Programmteil nennen wir Vorbedingung und eine Zusicherung nach dem Programmteil Nachbedingung .
Ein kleines Beispiel könnte so aussehen:
// Vorbedingung P: y > 1
x := y^2;
// Nachbedingung Q: x > y > 1
Wenn also die Vorbedingung gilt, so gilt natürlich auch die Nachbedingung , wenn wir das (kurze) Programm durchlaufen haben.
Verifikation und Korrektheit
Eine Verifikation prüft nun ob das Programm bestimmte Zusicherungen erfüllt. Wenn wir also eine Anweisungsfolge haben, und eine Vorbedingung , möchten wir verifizieren können, dass nach Abarbeitung von mit Vorbedingung , die Nachbedingung folgt.
Wir definieren uns hierfür zwei Arten der Korrektheit:
1. Partielle Korrektheit
Wenn die Ausführung von in einem Zustand, der genügt, beginnt und falls die Ausführung von nach endlich vielen Schritten terminiert, dann erfüllt der erreichte Zustand .
Schreibweise:
2. Totale Korrektheit
Siehe (1). Verlangt zusätzlich den Beweis der Terminierung.
Schreibweise:
Unsere (imperativen) Programme bestehen nicht nur aus einzelnen Anweisungen, sondern aus linearen Strukturen (z.B. Folgen von Anweisungen, Schleifen, …). Dies bedeutet wiederum, dass man aus der Korrektheit einzelner Teile des Programms die korrektheit des gesamten Programms beweisen kann.
Der wp-Kalkül beruht hierbei auf der Rückwärtsanalyse. Man stellt sich also die Frage: Welche Vorbedingung muss mindestens erfüllt sein, damit nach dem Ablauf des Programms eine gegebene Nachbedingung vorliegt? Das „mindestens“ ist sehr wichtig, denn es gibt für die eine Nachbedingung mehrere Vorbedingungen von denen uns aber nur die schwächste (weakest precondition) interessiert, daher auch der Name des Kalküls.
Definitionen und Regeln
Der wp-Kalkül ist wie folgt definiert:
Wenn ein Prädikat ist und eine Anweisungsfolge ist, dann ist die schwächste Vorbedingung von in Bezug auf , , ein Prädikat, das die Menge aller Startzustände beschreibt, so dass wenn die Ausführung von in einem der Zustände beginnt, die Ausführung in einem Zustand, der genügt, terminiert.
Schreibweise: .
ist schwächste Vorbedingung.
Zusätzlich seien noch folgende Definitionen gegeben:
“Wenn nichts passiert ändern sich die Bedingungen nicht.”
“Fehler dürfen nicht passieren.”
“Nach Abbruch gilt nichts mehr.”
Daraus lassen sich die folgenden Rechenregeln ableiten ( bzw seien hierbei beliebige Anweisungsfolgen):
Ausgeschlossene Wunder.
Distributivität der Konjunktionen.
Distributivität der Disjunktionen.
Wenn dann
Monotonie.
Seuqenzregel.
Wenn gilt:
Erweiterte Sequenzregel.
, wobei eine Variable und ein Ausdruck
Zuweisungsregel.
wenn seiteneffektfrei
Fallunterscheidung (If-Then-Else-Regel).
Fallunterscheidung (If-Then-Regel ohne Else-Teil).
Schleifen
Schleifen sind etwas komplizierter zu verifizieren, da hier ein Programmabschnitt so oft wiederholt wird (potentiell unendlich oft), bis eine Schleifenbedingung nicht mehr erfüllt ist.
Man beachte, dass man jede Art von Schleife auf die while
-Schleife zurückführen kann, so dass wir im wp-Kalkül nur while
-Schleifen betrachten.
Man könnte eine Schleife als geschachtelte Form von if-then-else
Anweisungen betrachten, in etwa dieser Form:
wp("while B do S") =
¬B ⇒ Q ∧
B ⇒ wp(S, ¬B ∧ Q ∧
B ⇒ wp(S, ¬B ⇒ Q ∧
B ⇒ wp(S, …)))
Dies ist absolut unhandlich und praktisch nicht zu lösen, daher gibt es einen anderen Lösungsweg.
Hierfür sucht man sich (durch „raten“) als erstes eine Schleifeninvariante . Dies ist eine Zusicherung, die vor, während und nach der Schleife gelten muss. Wichtig ist, dass dieser Ausdruck möglichst stark ist, und eine Aussage über den Bereich der Laufvariable, sowie den jeweils aktuellen Wert der zu berechnenden Variable in Abhängigkeit von der Laufvariablen enthält!
Das Schema einer Schleife sieht demnach folgendermaßen aus ( ist die Anweisungsfolge im Schleifenrumpf):
{I}
while B do
{I ∧ B} S {I}
{I ∧ ¬B}
{Q}
Hat man seine Invariante gefunden, kann man nun nach folgendem Rezept vorgehen um die Schleife zu verifizieren:
- Zeige dass vor der Schleife gilt. (nicht via wp-Kalkül)
- Beweise über wp-Kalkül:
- Zeige:
- Zeige dass Schleife terminiert.