Grundlagen der Informatik I “Programmierung”
Grundlagen der Informatik I “Programmierung” Grundlagen der Informatik I “Programmierung”
Die Grundüberlegung, die zu einem sehr vielseitigen Verifikationskalkül geführt hat, ist die folgende. Gegeben sei eine Folge von Anweisungen inst1;...;instn samt Vorbedingung pre und Nachbedingung post. Um zu beweisen, daß { pre} inst1;...;instn { post} wahr ist, überlegt man sich, was die – im logischen Sinne – schwächste Vorbedingung pren ist, so daß gilt { pren} instn { post} pren ist dann die Nachbedingung für den Algorithmus ohne den letzten Schritt instn und wir können das Verfahren wiederholen, bis wir eine Vorbedingung pre1 für inst1 gefunden haben, die schwächer ist als die Vorbedingung pre, d.h. für die pre ⇒ pre1 gilt. Ist dies gelungen, so ist der Algorithmus als korrekt bewiesen worden. 15 Für Konstrukte wie Schleifen wird dies natürlich etwas aufwendiger, da man hier darüber nachdenken muß, was sich während eines Schleifendurchlaufs verändert und was nicht – also invariant bleibt. In dem Kalkül von Dijkstra [Dijkstra, 1976] geht man sogar noch einen Schritt weiter. Dort wird zu jeder Instruktionsart eine Berechnungsvorschrift angegeben, wie man von einer vorgegebenen Nachbedingung post und einer Instruktion instruction zu derjenigen Vorbedingung pre kommt, welche die geringsten Forderungen stellt und dennoch { pre} instruction { post} garantiert. Diese Berechnungsvorschrift wp (für weakest precondition) ist eine Prädikatentransformation wp : Instruction×Predicate → Predicate derart, daß für alle Anweisungen instruction, alle Nachbedingungen post und alle Vorbedingungen pre gilt: { wp(instruction,post)} instruction { post} und { pre} instruction { post} ⇒ ( pre ⇒ wp(instruction,post) ) D.h. wp(inst,post) ist eine korrekte Vorbedingung und sie ist schwächer als alle anderen. Beispiel 4.2.5 (Weakest Precondition) Für die Nachbedingung x≥5 ∧ y≥0 erhalten wir bei verschiedenen Anweisungen folgende schwächste Vorbedingungen: 16 wp( x:=y+5 , x≥5 ∧ y≥0 ) ≡ y≥0 wp( y:=0 ; x:=y+5 , x≥5 ∧ y≥0 ) ≡ true wp( x:=y+5 ; y:=y-4 , x≥5 ∧ y≥0 ) ≡ y≥4 Leider sind die schwächsten Vorbedingungen für Schleifen und Prozeduren sehr unhandlich, da sie Existenzquantoren einführen und man bei Verwendung der mechanisch konstruierten Vorbedingung in einem Dschungel von Quantoren stecken bleibt. Aus diesem Grunde werden wir nicht direkt im Kalkül von Dijkstra arbeiten sondern in dem etwas schwächeren von Hoare, bei dem man die Vorbedingungen selbst finden muß. Als Vorschläge für Vorbedingungen, die wir nicht mehr beweisen müssen, werden wir dennoch die Prädikatentransformation mitbenutzen. Diese Vorschläge werden wir jedoch meist in stärkere, aber einfachere Zusicherungen überführen müssen, die weniger Quantoren enthalten. Hierzu werden wir ein gehöriges Maß an Intuition benötigen, da die Frage nach einer schematischen, aber korrekten Vereinfachung von Vorbedingungen immer noch ein Thema der Grundlagenforschung ist. Anstelle mit der schwächsten Vorbedingung rückwärts zu gehen, kann man übrigens auch versuchen, vorwärts zu gehen und die stärkste Nachbedingung sp (strongest postcondition) zu finden. Diese spielt aber für die 14 Sinnvoller ist es, ausgehend von der Nachbedingung der Spezifikation, das Programmstück zu konstruieren und im Laufe des Konstruktionsprozesses auf eine Vorbedingung zu kommen, die weniger restriktiv ist als die Vorbedingung der Spezifikation. 15 Diese Vorgehensweise läßt sich auch für die simultane Entwicklung von Programm und Beweis einsetzen, wenn man zu Beginn bereits eine ungefähre Idee im Kopf hat, wie der Algorithmus vorgehen soll. In diesem Fall konstruiert man in jedem Schritt die entsprechende Anweisung insti gleichzeitig mit prei. 16 Wir benutzen das Symbol ≡ anstelle des Gleichheitssymbols =, um Gleichheit von Prädikaten auszudrücken und Verwechs- lungen mit Gleichheiten innerhalb von Zusicherungen zu vermeiden.
Programmkonstruktion keine Rolle, da die Nachbedingung die Wirkung eines Programmstücks festlegt und damit im Programm “zurückgerechnet” wird. Die “Vorwärtsrechnung” von der Vorbedingung (zumeist true) zur Nachbedingung wirkt ziellos, da die Vorbedingung keine Information über das Ziel in sich trägt. Bei all diesen Überlegungen wurden die folgenden Regeln ohne weitere Begründung verwendet: pre ⇒ pre’, { pre’} instruction { post} { pre} instruction { post} { pre} instruction { post’} , post’ ⇒ post { pre} instruction { post} Verstärkung der Vorbedingung (VV) Abschwächung der Nachbedingung (AN) Abbildung 4.1: Verstärkungs- und Abschwächungsregeln für Programmbeweise Die Verstärkung der Vorbedingung bzw. die Abschwächung der Nachbedingung erlaubt uns, einen Programmbeweis in folgender Form zu beschreiben: Programm Zusicherungen Prämissen { pre} pre ⇒ pre’ { pre’} instruction { post’} post’ ⇒ post { post} Vor jeder und nach jeder Anweisung instruction steht eine Reihe von Zusicherungen, die sich von oben nach unten jeweils abschwächen. Am rechten Rand wird zusätzlich angegeben, warum diese Abschwächung zulässig ist. Das Lesen (und meist das Erzeugen) des Programmbeweises erfolgt dann von unten nach oben. Folgt oberhalb einer Zusicherung wieder eine Zusicherung, so beschreiben die Anmerkungen der oberen Bedingung, warum diese Verschärfung zulässig ist. Folgt eine Instruktion, so ist deren Vorbedingung über die jeweilige Regel für diesen Instruktionstyp z.B. über die schwächste Vorbedingung nachzuweisen. Zwei weitere Regeln sind sinnvoll, falls die Prädikate zu kompliziert werden: { pre} instruction { post} , { pre’} instruction { post} { pre ∨ pre’} instruction { post} Kombination der Vorbedingungen { pre} instruction { post} , { pre} instruction { post’} { pre} instruction { post ∧ post’} Kombination der Nachbedingungen Abbildung 4.2: Regeln für die Kombination von Zusicherungen in Programmbeweisen Diese beiden Regeln erlauben es, ein Programm einzeln für Teilzusicherungen zu prüfen. Die erste Regel gibt an, daß bei mehreren alternativen Vorbedingungen (pre ∨ pre’), die zweite Regel, daß bei mehreren Anforderungen (post ∧ post’) der Beweis einzeln geführt werden kann. Wir wollen nun im folgenden Abschnitt für alle Programmkonstrukte der Sprache Eiffel weitere formale Regeln angeben, die uns sagen, wie die Korrektheit einer komplexeren Anweisung aus Eigenschaften ihrer Bestandteile abgeleitet werden kann. Diese Regeln reichen aber noch nicht aus, um ein Programm zu beweisen. Hinzu kommen müssen die Regeln aus den jeweiligen Anwendungsgebieten. Soll etwas numerisch berechnet werden, so benötigen wir natürlich die Regeln der Arithmetik mit dem zusätzlichen Wissen über die Eigenschaften der Funktionen, die verwendet und berechnet werden sollen: z.B. a>b ⇒ ggt(a-b,b) = ggt(a,b). Diese Regeln werden im folgenden als bekannt vorausgesetzt, ohne deren Kalkül explizit anzugeben. Als Schlußbemerkung wollen wir noch anfügen, daß alle bisherigen Verifikationsmechanismen ausschließlich für Programme mit Copy-Semantik entwickelt wurden. Die Regeln, die wir im folgenden angeben werden, können
- Seite 110 und 111: class ARRAY[X] creation make featur
- Seite 112 und 113: class ARRAY[X] creation make featur
- Seite 114 und 115: formulieren und zu überwachen. Eig
- Seite 116 und 117: 3.8.2 Export geerbter Features Norm
- Seite 118 und 119: Ist also p ein Personenobjekt und a
- Seite 120 und 121: Definition 3.8.5 (Konformität) Ein
- Seite 122 und 123: Um die Beschreibung des Typsystems
- Seite 124 und 125: Nachkommenklassen aber folgt entity
- Seite 126 und 127: deferred class LIST[X] feature leng
- Seite 128 und 129: class STUDENT feature universität:
- Seite 130 und 131: Das Problem ist nun, daß beide Elt
- Seite 132 und 133: Prinzipiell wäre es sogar möglich
- Seite 134 und 135: Das bedeutet also, daß die Erbenkl
- Seite 136 und 137: Die eigentliche Montage eines Syste
- Seite 138 und 139: Verständlichkeit der Module: Die L
- Seite 140 und 141: dynamische Semantik: Die dynamische
- Seite 142 und 143: Constant ::= Manifest constant | Id
- Seite 145 und 146: Kapitel 4 Systematische Entwicklung
- Seite 147 und 148: 4.1.2 Grundideen des objektorientie
- Seite 149 und 150: jedoch dringend zu empfehlen, jegli
- Seite 151 und 152: • Ausleihe - Bücher werden nach
- Seite 153 und 154: Der Anwender ist kein echter Klient
- Seite 155 und 156: Es sei an dieser Stelle angemerkt,
- Seite 157 und 158: einen Rechner überprüft werden ka
- Seite 159: Definition 4.2.3 (Korrektheit von R
- Seite 163 und 164: Eine Wertzuweisung entity := Ausdru
- Seite 165 und 166: 4.3.2.1 Die Rolle formaler Paramete
- Seite 167 und 168: Für eine korrekt implementierte Pr
- Seite 169 und 170: { pre} Anweisung1 { p} , { p} Anwei
- Seite 171 und 172: die alle dieselbe Variable x betref
- Seite 173 und 174: 4.3.4.4 Verifikation Die Verifikati
- Seite 175 und 176: Dieser Beweis ist in der folgenden
- Seite 177 und 178: Im allgemeinen ist es sehr schwieri
- Seite 179 und 180: from Init invariant Inv variant Var
- Seite 181 und 182: Programm Zusicherungen Prämissen n
- Seite 183 und 184: entdeckten Fehler stillschweigend h
- Seite 185 und 186: Gegenlesen nicht findet(, aber auch
- Seite 187 und 188: 4.3.10 Die Verifikation rekursiver
- Seite 189 und 190: • Die Variante einer rekursiven R
- Seite 191 und 192: Die Art der Anweisungen ist nicht a
- Seite 193 und 194: Die Formulierung von Ausdrücken mu
- Seite 195 und 196: 4.4.6 Sprachbeschreibung Die nun fo
- Seite 197 und 198: Programm nachträglich als korrekt
- Seite 199 und 200: Der vollständige Korrektheitsbewei
- Seite 201 und 202: Dies ist die Grundidee der sogenann
- Seite 203 und 204: Als Initalanweisung genügt es, i m
- Seite 205 und 206: Beispiel 4.5.15 (Vertauschen von Se
- Seite 207 und 208: Mit einer wichtigen strategischen A
- Seite 209: Seien Sie sich bewußt, daß gerade
Die Grundüberlegung, die zu einem sehr vielseitigen Verifikationskalkül geführt hat, ist die folgende. Gegeben<br />
sei eine Folge von Anweisungen inst1;...;instn samt Vorbedingung pre und Nachbedingung post. Um zu<br />
beweisen, daß<br />
{ pre} inst1;...;instn { post}<br />
wahr ist, überlegt man sich, was die – im logischen Sinne – schwächste Vorbedingung pren ist, so daß gilt<br />
{ pren} instn { post}<br />
pren ist dann die Nachbedingung für den Algorithmus ohne den letzten Schritt instn und wir können das<br />
Verfahren wie<strong>der</strong>holen, bis wir eine Vorbedingung pre1 für inst1 gefunden haben, die schwächer ist als<br />
die Vorbedingung pre, d.h. für die pre ⇒ pre1 gilt. Ist dies gelungen, so ist <strong>der</strong> Algorithmus als korrekt<br />
bewiesen worden. 15 Für Konstrukte wie Schleifen wird dies natürlich etwas aufwendiger, da man hier darüber<br />
nachdenken muß, was sich während eines Schleifendurchlaufs verän<strong>der</strong>t und was nicht – also invariant bleibt.<br />
In dem Kalkül von Dijkstra [Dijkstra, 1976] geht man sogar noch einen Schritt weiter. Dort wird zu je<strong>der</strong><br />
Instruktionsart eine Berechnungsvorschrift angegeben, wie man von einer vorgegebenen Nachbedingung post<br />
und einer Instruktion instruction zu <strong>der</strong>jenigen Vorbedingung pre kommt, welche die geringsten For<strong>der</strong>ungen<br />
stellt und dennoch { pre} instruction { post} garantiert. Diese Berechnungsvorschrift wp (für<br />
weakest precondition) ist eine Prädikatentransformation<br />
wp : Instruction×Predicate → Predicate<br />
<strong>der</strong>art, daß für alle Anweisungen instruction, alle Nachbedingungen post und alle Vorbedingungen pre gilt:<br />
{ wp(instruction,post)} instruction { post}<br />
und { pre} instruction { post} ⇒ ( pre ⇒ wp(instruction,post) )<br />
D.h. wp(inst,post) ist eine korrekte Vorbedingung und sie ist schwächer als alle an<strong>der</strong>en.<br />
Beispiel 4.2.5 (Weakest Precondition)<br />
Für die Nachbedingung x≥5 ∧ y≥0 erhalten wir bei verschiedenen Anweisungen folgende schwächste<br />
Vorbedingungen: 16<br />
wp( x:=y+5 , x≥5 ∧ y≥0 ) ≡ y≥0<br />
wp( y:=0 ; x:=y+5 , x≥5 ∧ y≥0 ) ≡ true<br />
wp( x:=y+5 ; y:=y-4 , x≥5 ∧ y≥0 ) ≡ y≥4<br />
Lei<strong>der</strong> sind die schwächsten Vorbedingungen für Schleifen und Prozeduren sehr unhandlich, da sie Existenzquantoren<br />
einführen und man bei Verwendung <strong>der</strong> mechanisch konstruierten Vorbedingung in einem Dschungel<br />
von Quantoren stecken bleibt. Aus diesem Grunde werden wir nicht direkt im Kalkül von Dijkstra arbeiten son<strong>der</strong>n<br />
in dem etwas schwächeren von Hoare, bei dem man die Vorbedingungen selbst finden muß. Als Vorschläge<br />
für Vorbedingungen, die wir nicht mehr beweisen müssen, werden wir dennoch die Prädikatentransformation<br />
mitbenutzen. Diese Vorschläge werden wir jedoch meist in stärkere, aber einfachere Zusicherungen überführen<br />
müssen, die weniger Quantoren enthalten. Hierzu werden wir ein gehöriges Maß an Intuition benötigen, da die<br />
Frage nach einer schematischen, aber korrekten Vereinfachung von Vorbedingungen immer noch ein Thema<br />
<strong>der</strong> <strong>Grundlagen</strong>forschung ist.<br />
Anstelle mit <strong>der</strong> schwächsten Vorbedingung rückwärts zu gehen, kann man übrigens auch versuchen, vorwärts<br />
zu gehen und die stärkste Nachbedingung sp (strongest postcondition) zu finden. Diese spielt aber für die<br />
14 Sinnvoller ist es, ausgehend von <strong>der</strong> Nachbedingung <strong>der</strong> Spezifikation, das Programmstück zu konstruieren und im Laufe des<br />
Konstruktionsprozesses auf eine Vorbedingung zu kommen, die weniger restriktiv ist als die Vorbedingung <strong>der</strong> Spezifikation.<br />
15 Diese Vorgehensweise läßt sich auch für die simultane Entwicklung von Programm und Beweis einsetzen, wenn man zu<br />
Beginn bereits eine ungefähre Idee im Kopf hat, wie <strong>der</strong> Algorithmus vorgehen soll. In diesem Fall konstruiert man in jedem<br />
Schritt die entsprechende Anweisung insti gleichzeitig mit prei.<br />
16 Wir benutzen das Symbol ≡ anstelle des Gleichheitssymbols =, um Gleichheit von Prädikaten auszudrücken und Verwechs-<br />
lungen mit Gleichheiten innerhalb von Zusicherungen zu vermeiden.