wurde gezeigt, wie im Prinzip die Korrektheit einer - oth

Werbung
Algorithmen und Datenstrukturen
Fachhochschule Regensburg
3. Übungsblatt
Korrektheit, Verifikation, Zusicherungen,
partielle und totale Korrektheit, Hoare-Kalkül
Algorithmen und Datenstrukturen
Name: ________________________
Lehrbeauftragter: Prof. Sauer
Vorname: _____________________
Partielle Korrektheit
Falls ein Programm terminiert und die Spezifikation erfüllt, heißt es partiell korrekt
Die Hoare-Formel {P}S{Q} ist gültig, wenn S partiell korrekt ist bzgl. Vorbedinging P und Nachbedingung
Q.
S
Anfangszustand
Endzustand
Ausführung
P gilt
Q gilt, wenn S terminiert
d.h.: Wenn P im Anfangszustand von S gilt und wenn S terminiert, dann gilt Q nach Ausführung von S
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 = Partielle Korrektheit und Terminierung
Ein Programm, das immer terminiert und partiell korrekt ist, heißt total korrekt.
Die Hoare-Formel {P}S{Q} ist gültig, wenn S total korrekt ist bzgl. Vorbedinging P und Nachbedingung Q
S
Anfangszustand
Endzustand
Ausführung
P gilt
Q gilt und S terminiert
Bsp.:
1. Partielle Korrektheit nicht aber totale Korrektheit zeigt: {true} while (x!= 0) x = x-1;{x ==
0}, da keine Terminierung bzgl. x < 0.
2. Die Hoare Formel {x>0} while (x > 0) x = x + 1; {false} terminiert nie. Sie ist partiell korrekt, aber nicht
total total korrekt.
Generell drückt die Gültigkeit von {P} S {false} Nichtterminierung aus, d.h. {P} S{false} ist partiell korrekt, S
terminiert aber nicht für alle Anfangszustände, die P erfüllen.
Halteproblem
Gibt es ein Programm, das für ein beliebiges anderes Programm entscheidet, ob es für eine bestimmte
Eingabe in eine Endlosschleife gerät oder nicht?
Das allgemeine Halteproblem dückt sich offenbar in folgender Frage aus: „Hält Algorithmus x bei der
Eingabe von y?“ und ist nicht entscheidbar.
Anschaulicher Beweis der Unentscheidbarkeit des Halteproblems
1
Algorithmen und Datenstrukturen
Annahme: Es gibt eine Maschine (Algorithmus) STOP mit 2 Eingaben: Algorithmentext xund eine Eingabe
y für 2 Ausgaben („JA: stoppt bei der Eingabe von y“, „NEIN“ stoppt nicht bei der Eingabe von y“)
x
JA
STOP
y
NEIN
Mit dieser Maschine STOP kann man eine neue Maschine SELTSAM konstruieren:
x
JA
x
STOP
OK
x
NEIN
Bei Eingabe von x wird getestet, ob x bei Eingabe von x stoppt. Im JA-Fall wird in eine Endlosschleife
gegangen, die nie anhält. Im NEIN-Fall hält SELTSAM an der Anzeige OK.
Es erfolgt nun die Eingabe von SELTSAM (für sich selbst) mit der Frage: „Hält SELTSAM bei der Eingabe
von SELTSAM?
1. Wenn JA, wird der JA-Ausgang von STOP angelaufen und SELTSAM gerät in eine Endlosschleife, hält
also nicht. Widerspruch!
2. Wenn NEIN, so wird der NEIN-Ausgang von STOP angelaufen und SELTSAM stoppt mit OK.
Widerspruch!
Der Widerspruch folgt aus der Annahme, dass eine STOP-Maschine existiert, was verneint werden muß.
Nicht entscheidbare Probleme
Das Theorem von Rice: Alle nicht trivialen (nicht einfachen) Eigenschaften von Algorithmen sind
unentscheidbar
Als trivial gelten bspw. folgende Eigenschaften:
- Der Algorithmus hat eine bestimmte Länge
- Der Algorithmus enthält eine bestimmte Zeichenkette
Nicht trivial sind hingegen die Eigenschaften, ob er eine bestimmte Ausgabe erzeugt bzw. ob eine
bestimmte Anweisung ausgeführt wird etc.
2
Algorithmen und Datenstrukturen
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
Wenn die Prämissen 1 bis n bewiesen sind, dann ist auch die Konklusion bewiesen. Manche Regeln
haben zusätzliche Bedingungen, die festlegen, wann sie angewendet dürfen.
Die Anwendung der Regeln des Hoare-Kalküls führt auf das Hoare Tripel, d.h. auf Gruppen von 3
Elemneten folgender Art: {P}S{Q} 1 . 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:
-
Finde eine Zwischenbedingung {P ' } für 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
-
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
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 ' } .
1
Hoare-Formel
3
Algorithmen und Datenstrukturen
Die Regeln nach C. A. R. Hoare
R0: Axiom der leeren Anweisung (Skip-Regel)
{P}NOP{P}
R1: Zuweisung (Zuweisungsaxiom), Regel für die Ergibt-Anweisung
{P[ x / t ]} x = t ;{P}
Dies ist neben der Skip-Regel die einzige Regel, die ohne vorher zu beweisende Elemente auskommt.
Für Korrektheitsbeweise bildet sie den Basisfall, auf dem die anderen Regeln aufbauen können. Die
Regel ist so zu lesen: Die Vorbedingung einer Zuweisung x = t ist genau die Formel, die entsteht, wenn
man die Nachbedingung alle Vorkommen von x durch t ersetzt.
R2: Abschwächung
P1 ⇒ P, {P}S{Q}, Q ⇒ Q1
{P1}S{Q1}
Abschwächung wird benötigt, um aus speziellen Vorbedingungen, wie sie z.B. durch die Zuweisungsregel
entstehen, auf allgemeinere Bedingungen schließen zu können.
Bsp.:
n == 3 ⇒ 2n >= 6, {2n >= 6}n = 2 * n;{n >= 6}, n >= 6 ⇒ n > 5
{n == 3}n = 2 * n;{n > 5}
R3: Regel für Anweisungsfolgen, Regel der sequentiellen Komposition, Sequenzregel
{P}S1{Q};{Q}S 2{R}
{P}S1; S 2{R}
Bsp.:
{true}
{5 == 5}
x = 5;
{x == 5}
{x * x + 6 == 31}
res = x * x + 6;
{res == 31}
{true}
x = 5;
res = x * x + 6;
{res == 31}
R4a: Regel der bedingten Fallunterscheidung, Regel der bedingten Anweisung
{P ∧ B}S1{Q}
{P ∧ ¬B}S 2 {Q}
{P}if B then S1 else S 2 {Q}
Die Fallunterscheidung besagt, dass sowohl der then- als auch der else-Zweig, wenn sie mit einer
gültigen Vorbedingung abgearbeitet werden, die gleiche Nachbedingung erfüllen.
B muß seiteneffektfrei sein, d.h. der Zustand des Programms darf bei der Auswertung von B nicht
verändert werden
Bsp.
4
Algorithmen und Datenstrukturen
{ true }
if (x < 0)
res = -x;
else
res = x;
{ res =|x|}
denn {x < 0 }
{-x == |x|}
und {!x < 0}
{x == |x|
R4b: Regel der bedingten Fallunterscheidung, Regel der bedingten Anweisung. Diese Variante von dient
R4a dient der Verifikation von Verzweigungen ohne Alternative (den "else"-Teil).
{P ∧ B}S{Q}
{P ∧ ¬B} ⇒ {Q}
{P}if B then S
Allgemein kann man hier die Regel 4a anwenden und den Quelltext unter S2 als NOP ansehen.
Bsp.:
{true}
{true}
{y == y }
res = y;
res = y;
if (x > y) res = x;
{res == y}
{res == max(x,y)}
if (x > y) res = x;
{res == max(x,y)}
R5a : Iteration
Regel Iterationpartiell
2
{I ∧ B}S{I }
{I }while B do S {I ∧ ¬B}
Regel Iterationtotal
3
{I ∧ B}S{I }
{I ∧ B ∧ t = z}S{t < z}
{I }while( B ) do S{I ∧ ¬B}
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 so genannte 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.
z ist eine logische Variable, die frei ist. Das bedeutet: Sie darf in I , B, S , t nicht vorkommen und dient
dazu, den alten Wert von t zwischenzuspeichern).
Offensichtlich ist die Regel für die totale Korrektheit eine Erweiterung der Regel für die partielle
Korrektheit. Es wird hier eine fundierte Ordnung verwendet: S muss einen Integer-Ausdruck t echt
kleiner werden lassen, während die Invariante garantiert, dass der Ausdruck den kleinsten Wert nicht
unterschreitet.
Bsp.:
- partielle Korrektheit: I ≡ ( n <= end + 1)
2
3
Wenn B seiteneffektfrei
Wenn B seiteneffektfrei, I ⇒ t ≥ 0 und
z ist freie logische Variable
5
Algorithmen und Datenstrukturen
{n <= end & &n <= end + 1}n = n + 1;{n <= end + 1}
{n <= end + 1 & &!(n <= end )}
- totale Korrektheit: t ≡ end + 1 − n
{n <= end & &n <= end + 1}n = n + 1;{n <= end + 1}
{n <= end & &n <= end + 1 & &(end + 1 − n) == z}n = n + 1;{(end + 1 − n < z}
{n <= end + 1}while(n <= end )n = n + 1;{n <= end + 1 & &!(n <= end )}
R5b :
{I }S{R}
{R ∧ ¬B}S 2 {R}
{I }repeatS _ until _ B{R ∧ B}
R6
{P ∧ ( B = w1 }S i {R} __(i = 1(1)n)
{P}case _ B _ of _ w1 : S1 ;...wn : S n {R}
R7a : Implikationsregel, Konsequenzregel 1 (stärkere Vorbedingung)
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.
Bsp.: { true } x = 5; {x == 5}, denn
true ⇒ 5 == 5,{5 == 5}x = 5;{x == 5}
{true}x = 5;{x == 5}
R7b : Implikationsregel, Konsequenzregel 2 (stärkere Nachbedingung)
{P}S {Q}, Q ⇒ {R}
{P}S{R}
{true}x = 5;{x == 5}, x == 5 ⇒ x >= 5
Bsp.: { true } x = 5; {x >= 5}, denn
{true}x = 5;{x >= 5}
Das allgemeine Schema des partiellen Korrektheitsbeweises
Da Vor- und Nachbedingungen i.a. gegeben sind, liegt folgende Situation vor:
wobei
{P}S1 ; S 2 ;.....; S n {Q},
S1 ; S 2 ;....; S n die einzelnen Anweisungen des Programms sind.
Der Korrektheitsbeweis geht von hinten nach vorne vor, und zwar nach folgendem Schema:
1. Finde eine Zwischenbedingung
{α 1 }S n {Q} .
4
α1
für
S n und spalte den Beweis in {P}S1 ; S 2 ;.....; S n−1 {a1 } und
4
Für n = 1 hat der erste Teil die Gestalt {P}{α 1 } , was im nächsten Fall behandelt wird.
6
Algorithmen und Datenstrukturen
Die Weiterverarbeitung von
{α 1 }S n {Q} hängt von der Art der Anweisung
S n ab (Wertzuweisung, if (…)
… else … oder while(…) …). Für jede Anweisungsart benötigt man eine extra Regel. Diese Regeln geben
auch z.T Hinweise, was die richtige Zwischenbedingung α 1 ist.
2. Nach dem gleichen Schema werden die Anweisungen
schließlich die Situation
S n −1 ; S n − 22 ;.....; S1 behandelt, bis man
{P}{a n } erreicht, wo kein Programmstück zwischen den Bedingungen mehr
vorhanden ist. Partielle Korrektheit ist bewiesen, wenn man in dieser Situation
α n , mit rein mathematischen Mitteln beweisen kann
5
P ⇒ α n , d.h. aus P folgt
.
Die Anwendung der einzelnen Verifikationsregeln
1. Zuweisung
2. if (….) …. else …
3. while (…..) …… 6
Ausgangssituation:
{P}s1 ; s 2 ;...; s n−1 ; while( B) s{Q}, wobei
B die Testbedingung ist und s ein
Programmstück (Anweisungsfolge) ist.
Die while-Schleife transformiert dieses Problem in folgende einzelne Probleme:
1. Finde eine geeignete Schleifeninvariante
2. Finde eine geeignete Zwischenbedingung
α1
als neue Vorbedingung für die while-Anweisung, so
dass α 1 ⇒ I gilt, d.h. die Zwischenbedingung α 1 ist die spezielle Form der Schleifeninvariante,
die vor Eintritt in die Schleife gilt.
3. Verifiziere den Erhalt der Schleifeninvariante: I ∧ B s I . Dies bestätigt, dass die
{
}{}
Schleifeninvariante so lange gültig bleibt, wie die Bedingung B gilt.
4. Weise nach, dass die Schleifeninvariante stark genug ist, dass sie die Nachbedingung Q
erzwingt:
(I ∧ ¬B ⇒ Q ) , d.h. nachdem die Bedingung
B falsch geworden ist, und die Schleife
verlassen wurde, muß Q folgen.
5. Mache weiter mit den restlichen Anweisungen vor der Schleife für die Nachbedingung
{P}s1 ; s 2 ;...; s n−1 ; {α 1 }
Beispiele
5
6
dies entspricht der Abschwächungsregel
ohne break- und continue-Anweisungen
7
α1 :
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
Java kennt eine Anweisung assert BoolescherAusdruck, mit der zur Laufzeit sichergestellt werden
kann, dass Ausnahmen über den Programmzustand eingehalten werden. Die Anweisung bewirkt, dass
der Boolesche Ausdruck ausgewertet wird. Hat er den Wert true, dann passiert nichts weiter. Hat er den
Wert false, wird ein Fehler ausgelöst.
Man könnte die gleiche Wirkung auch mit if erzielen, z.B. könnte man statt assert 0 <=a; schreiben if
(!(0 <= a)) throw new AssertionError();.
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
8
Algorithmen und Datenstrukturen
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.
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
9
Herunterladen