Totale Korrektheit, Partielle Korrektheit, Hoare Kalkül, Assertions

Werbung
Algorithmen und Datenstrukturen
Übung 1c: Totale Korrektheit, Partielle Korrektheit, Hoare Kalkül, Assertions
(Zusicherungen)
Partielle Korrektheit
Falls ein Programm terminiert und die Spezifikation erfüllt, heißt es partiell korrekt
Terminierung
kann man allgemein nicht entscheiden, d.h. es gibt kein Verfahren, welches für einen beliebigen
Algorithmus feststellt, ob dieser terminiert oder nicht.
Totale Korrektheit
Ein Programm, das immer terminiert und partiell korrekt ist, heißt total korrekt.
Hoare Kalkül
Bei dem Hoare-Kalkül handelt es sich um eine Menge von Regeln, die sich aus Prämissen und
Schlussfolgerungen zusammensetzen:
Prämisse1
Prämisse2
...
Prämissen
--------------Konklusion
Die Anwendung der Regeln des Hoare-Kalküls führt auf das Hoare Tripel, d.h. auf Gruppen von 3
Elemeneten folgender Art: {P}S{Q} . Hier werden P und Q Zusicherungen genannt. S steht für eine
Anweisung (ein Statement) in einer Programmiersprache
Mit dem Hoare-Kalkül kann die partielle Korrektheit eines Programms nachgewiesen werden:
-
Zerlege den Algorithmus in seine einzelne Anweisungen und füge vor (und nach) jeder
Anweisung geeignete Vor- und Nachbedingungen ein. Zeige, dass die einzelnen Anweisungen
bzgl. dieser Bedingungen (partiell) korrekt sind.
Beweise die Korrektheit des gesamten Algorithmus aus der Korrektheit der einzelnen Aussagen.
Der Nachweis der Korrektheit erfolgt nach dem Schema:
{P}( s1 ;...; s n ){Q} .
Der Korektheitsbeweis geht von hinten nach vorne vor, und zwar nach folgendem Schema:
s n und spalte den Beweis in {P}( s1 ;...; s n−1 ){P'} und
{P' }( s n ){Q} . Die Weiterverarbeitung von {P'}( s n ){Q} hängt von der Art der Anweisung s n
-
Finde eine Zwischenbedingung {P ' } für
-
ab. Für jede Anweisungsart benötigt man eine Extraregel. Diese Regeln geben auch z.T.
Hinweise, was die richtige Zwischenbedingung ist.
Nach dem gleichen Schema werden die Anweisungen s n−1 ;...; s1 behandelt, bis man schließlich
eine Situation erreicht
{P}(){Pn '} erreicht, wo kein Programmstück zwischen den Bedingungen
1
Algorithmen und Datenstrukturen
mehr vorhanden ist. Partielle Korrektheit ist bewiesen, wenn man in dieser Situation
{P} {Pn '} mit rein mathematischen Mitteln erreichen kann.
Verifikationsregeln schreibt man folgendermaßen:
{P1 }s1{Q1 },..., {Pn }s n {Qn }
. Sie hat die Bedeutung:
{P}s{Q}
Wenn Aussagen oberhalb der Trennlinie wahr sind, dann ist auch die Aussage unterhalb wahr. Eine
Voraussetzung (oder ein Effekt) {P} wird abgeschwächt zu {P ' } , wenn gilt {P}
{P ' } .
Die Regeln nach C. A. R. Hoare
R0: Axion der leeren Anweisung
{P}NOP{P}
R1: Zuweisungen IZuweisungsaxiom, Regel für die Ergibt-Anweisung
x
{PE }x = E{P}
x
Hier bedeutet PE , dass E durch x substituiert werden muß, damit die Nachbedingung P wahr wird. Da
diese Regel keine Prämissen hat, wird sie auch als Axiom bezeichnet.
R2: Regel für Anweisungsfolgen, Regel der sequentiellen Komposition, Sequenzregel
{P}S1{Q};{Q}S 2 {R}
{P}S1 ; S 2 {R}
R3a: Regel der bedingten Fallunterscheidung, Regel der bedingten Anweisung
{P ∧ B}S1{R}
{P ∧ ¬B}S 2 {R}
{P}if _ B _ then _ S1 _ else _ S 2 {R}
R3b: Regel der bedingten Fallunterscheidung, Regel der bedingten Anweisung. Diese Variante von dient
R3a dient der Verifikation von Verzweigungen ohne Alternative (den "else"-Teil).
{P ∧ B}S{R}
{P ∧ ¬B} {R}
{P}if _ B _ then _ S
Allgemein kann man hier die Regel 3a anwenden und den Quelltext unter S2 als NOP ansehen.
R4a
{I ∧ B}S{I }
{I }while _ B _ do _ S {I ∧ ¬B}
2
Algorithmen und Datenstrukturen
Bei while-Schleifen wird der Rumpf S solange wiederholt, bis die Wiederholungsanweisung B nicht mehr
erfüllt ist (also ¬B gilt). Zur Verifikation von Schleifen ist es nötig, eine sogenammte Invariante zu finden.
Invarianten gelten nach jedem Schleifendurchlauf und beschreiben innerhalb der Schleifendynamik das
Gleichbleibende. Das Finden von Invarianten ist eine der Schwierigkeiten des Hoare-Kalküls. Einen
eindeutigen und sicheren Weg zur Bestimmung von Invarianten gibt es nicht.
R4b :
{I }S{R}
{R ∧ ¬B}S 2 {R}
{I }repeatS _ until _ B{R ∧ B}
R5
{P ∧ ( B = w1}S i {R} __(i = 1(1)n)
{P}case _ B _ of _ w1 : S1 ;...wn : S n {R}
R6a : Implikationsregel
{P}S {Q}, Q
{R}
{P}S{R}
R6b : Implikationsregel
P
{R}, {R}S {Q}
{P}S{Q}
Wenn aus einem Zustand P ein Zustand R folgt, aus dem über den Quelltext ein Zustand Q
angenommen wird, dann folgt Q bereits aus P.
3
Algorithmen und Datenstrukturen
Assertions (Zusicherungen)
Aufgabe
Assertions sind sinnvoll, wenn
-
-
der Programmierer überprüfen will, ob sein Programm an entscheidenden Stellen die Ergebnisse
liefert, die zu erwarten wären. Sonderformen dieser Korrektheits-Checks sind: Preconditions
(Vorbedingungen) und Postconditions (Nachbedingungen). Preconditions sichern korrekte
Übergabewerte ab, Postconditions überprüfen am Ende der Methode, ob das Ergebnis sinnig ist
und den Erwartungen entspricht. Vor- und Nachbedingungen einzelner Programmteile können
zum Nachweis der Korrektheit eines Programms benutzt werden. Andere Anwendungen
erschließen sich bei komplexen, undurchsichtigen Schleifen, wo jeder Durchlauf kontrolliert wird.
man strikt die Überprüfung nach Korrektheit (Assertions) vom Programmfluß trennen (if-else)
trennen will.
man sichergehen will, dass bestimmte Bereiche beim normalen Betrieb nicht durchlaufen werden.
Assertions sollten nicht verwendet werden, wenn
-
wenn Eingaben von Benutzern oder Dateien, Datenbanken etc. überprüft werden sollen. Bei
Assertions wird davon ausgegangen, dass der "boolesche Ausdruck" immer erfüllt ist, solange
das Programm korrekt läuft. Bei Benutzereingaben kann man aber zu keiner Zeit davon
ausgehen. Sie können zu jeder Zeit falsch bzw. unsinnig sein.
Das assert-Makro in C++ zum Verifizieren von Vor- und Nachbedingungen
Zusicherungen werden in C++ mit dem Header <cassert> eingebunden, z.B.:
#include <cassert>
// enthaelt Makrodefinition
const int grenze = 100;
int Iindex;
// .. Berechnung des Index
// .. Test auf Einhalten der Grenzen
assert(index >= 0 && index < grenze)
...
Falls die Annahme (index >= 0 && index < grenze) nicht stimmen sollte, wird das Programm mit
einem Fehler abgebrochen, die die zu verifizierende logische Annahme, die Datei und die Nummer der
Zeile enthält, in der der Fehler aufgetreten ist.
Assertions in Java
Assertions werden im Java-Quellcode folgendermaßen genutzt:
-
assert Ausdruck
assert Ausdruck1 : Ausdruck2
Der Ausdruck hinter dem Schlüsselwort assert muß in jedem Fall einen booleschen Wert zurückliefern.
In der Regel wird der Ausdruck so gewählt, dass er bei korrekter Arbeitsweise des Programms immer
true ergibt. Man spricht deshalb auch von Invarianten. Arbeitet das Programm nicht korrekt und liefert
der Ausdruck false zurück, dann wird ein AssertionError ausgelöst.
Die zweite Variante erlaubt es, hinter dem Doppelpunkt eine zweite Anweisung an die assert-Anweisung
zu übergeben: diese setzt den Fehlertext.
Bsp.: Eine Methode div() muß u.a. mit einer Zahl ungleich 0 versorgt werden. Sollte irgendein
Programmteil fehlerhaft sein und den Divisor doch mit 0 belegen, muß ein Assert-Error erfolgen.
4
Algorithmen und Datenstrukturen
public class Assertiontest
{
public static int div(int divident,int divisor)
{
assert divisor != 0 : "Bitte keine Zahl durch 0 teilen.";
return divident / divisor;
}
public static void main(String args[])
{
System.out.println("Quotient ist " + div(10,2));
System.out.println("Quotient ist " + div(10,0));
}
}
Damit das Programm kompiliert und ausgeführt werden kann, muß bei Compiler und Laufzeitumgebung
noch ein Schalter gesetzt werden, z.B.
javac –source 1.4 AssertionTest.java
Die Überprüfung von Assertions ist zur Laufzeit standardmäßig abgeschaltet. Dadurch entsteht kein
Geschwindigkeitsverlust bei der Ausführung der Programme. Zur Aktivierung von Assertions muß die
Laufzeitumgebung mit dem Schalter –ea (enable assertions) gestartet werden, z.B.:
java –ea AssertionTest
5
Herunterladen