8.6 Unentscheidbarkeit

Im vorherigen Teilkapitel haben wir die universelle Turingmaschine U konstruiert, die eine andere Turingmaschine, deren Codierung und Inputwort sie als Input gegeben hat, simulieren kann. Technisch gesprochen: U akzeptiert die Sprache

(1){cw | c=enc(M) und M akzeptiert w} .

Allerdings: wenn M auf x nicht terminiert, dann terminiert U auf enc(M)x auch nicht. U akzeptiert die Sprache also, entscheidet sie aber nicht. Wäre es nicht schön, eine Turingmaschine zu haben, die diese Sprache entscheidet? Dann könnten wir jede Turingmaschine simulieren und gleichzeitig Endlosschleifen und eventuell ganz allgemein "Programmierfehler" vorhersagen und abfangen. Wir werden zeigen, dass dies leider nicht möglich ist. In der Literatur ist dies als die Unentscheidbarkeit des Halteproblems (englisch undecidability of the Halting problem bekannt).

Als vorbereitenden Schritt schauen wir uns kurz die Codierungsfunktion nochmal genauer an. Wir bezeichnen mit TMΣ die Menge aller Turingmaschinen mit Inputalphabet Σ. Wir hatten die Codierungsfunktion enc:TMΣΛ definiert, für das Codierungsalphabet Λ:=Σ{0,1,#,,,L,S,R,;}. In diesem Teilkapitel wird es nötig sein, die Turingmaschine über dem Alphabet Σ selbst zu codieren. Dies ist nicht besonders schwierig, solange Σ mindestens zwei Zeichen hat. Wenn z.B. Σ die Zeichen 0 und 1 enthält, dann können wir alle Zeichen in Λ wiederum als Strings in Σ codieren. Wir müssen hier nur vorsichtig sein, dass der Code präfixfrei ist. Wenn wir zum Beispiel naiv 1 als 1 und 0 als 0 und # als 01 codieren, dann wissen wir nicht mehr, was mit dem Codewort 01 gemeint ist. Am einfachsten geht das mit einem Blockcode, in dem alle Codewörter die gleiche Länge k haben, also Λ{0,1}k. Mit k=log2|Λ| ist das kein Problem. Unsere "neue" Codierungsfunktion enc ist nun also enc:TMΣΣ. Wir definieren nun die Haltesprache HALTΣ:

HALT:={enc(M)w | M akzeptiert w} .

Wir können die universelle Turingmaschine U leicht abwandeln, dass sie HALT akzeptiert; wir müssen nur einen Decodierungsschritt vorausschicken, der die neue Codierung enc(M)Σ in unsere "alte" in Λ übersetzt. Beachten Sie, dass das Zeichen ;, das wir auch blockcodiert haben, uns hilft, die Grenze zwischen enc(M) und w zu erkennen.

Wir zeigen nun, dass HALT unentscheidbar ist, dass es also keine Möglichkeit gibt, das Nichtterminieren einer Maschine M auf Eingabe x vorauszusehen und abzufangen.

Theorem 8.6.1 (Unentscheidbarkeit des Halteproblems). Die Sprache HALT ist unentscheidbar.

Ich gebe erst einmal einen kurzen und knappen Widerspruchsbeweis. Falls das ihnen zu schnell ging, lesen Sie den zweiten Beweis, in dem ich mir mehr Zeit nehme.

Kurzer Beweis per Wiederspruch. Nehmen wir an, es gäbe eine Maschine H, die HALT entscheidet. Dann wäre auch die Sprache

Diag:={enc(M) | M akzeptiert enc(M)}

entscheidbar. Warum? Wir können einfach schauen, ob das Eingabewort c die Form enc(M) hat und in diesem Fall das Wort enc(M)enc(M) der Maschine H übergeben. Die Sprache Diag ist, salopp ausgedrückt, die Menge aller Turingmaschinen, die ihre eigene Codierung als Inputwort akzeptieren. Ebenso wäre auch

NegDiag:={enc(M) | M akzeptiert enc(M) nicht}

entscheidbar; wir müssen ja nur Diag entscheiden und dann das Ergebnis negieren. NegDiag ist sozusagen die Menge aller Turingmaschinen, die nicht ihre eigene Codierung als Inputwort akzeptieren. Da NegDiag nach Annahme entscheidbar ist, gibt es eine Maschine D, die NegDiag entscheidet.

Wir fragen uns jetzt: gehört enc(D) selbst zu NegDiag?

(nach Definition von NegDiag)enc(D)NegDiagD akzeptiert enc(D) nicht(Bedeutung der Notation L(D))enc(D)L(D)(nach Annahme L(D)=NegDiag)enc(D)NegDiag

Also enc(D)NegDiagenc(M)NegDiag, ein Widerspruch. Unsere Annahme, dass HALT entscheidbar sei, ist also falsch.

Ausführlicher Beweis. Ich finde Beweise durch Widerspruch immer etwas unintuitiv, weil man die ganze Zeit im Konjunktiv argumentieren muss. Daher hier ein Beweis ohne Widerspruch. Wir zeigen, dass HALT unentscheidbar ist, indem wir für eine beliebige Turingmaschine M zeigen, dass sie HALT nicht entscheidet, indem wir nämlich ein Eingabewort zΣ konstruieren, auf dem M scheitert, also entweder
  1. fH(z)=accept aber zHALT, oder
  2. fH(z)=reject aber zHALT , oder
  3. fH(z)=undefined, d.h. H terminiert auf Eingabewort z nicht.
Wir setzen nun y:=enc(D) und z:=yy, wobei D eine neue Turingmaschine ist, die wir auf Basis von H konstruieren werden. Also noch einmal. Für eine beliebige, uns gegebene Turingmaschine H, werden wir eine neue Turingmaschine D bauen und sie codieren als y:=enc(D), so dass entweder
  1. fH(yy)=accept aber yyHALT, oder
  2. fH(yy)=reject aber yyHALT, oder
  3. fH(yy)=undefined.
Wenn uns dies gelingt, so haben wir gezeigt, dass H nicht die Sprache HALT entscheidet: im Fall 3 terminiert H ja nicht einmal; in Fall 1 und 2 liefert H zwar eine Antwort, aber die falsche. Der Code für D ist sehr einfach:
def D(x):
    if H(xx) == accept then 
        reject 
    else 
        accept

Zur Erinnerung: y:=enc(D). Wir unterscheiden drei Fälle.

  • H(yy)=reject. Dann geht der Aufruf von D(y) also in den else-Teil in den Zeilen 4-5 und D(y)=accept, somit yy=enc(D)yHALT Wir befinden uns in Fall 1: yyHALT aber H(yy)=reject. Die Maschine H hat eine falsche Antwort für HALT geliefert.
  • H(yy)=accept. Dann geht der Aufruf von D(y) in Zeile 3, und D(y)=reject, somit yy=enc(D)yHALT. Wir befinden uns in Fall 2: yyHALT und H(yy)=accept. Die Maschine H hat abermals eine falsche Antwort geliefert.
  • H(yy) terminiert nicht. Dann befinden wir uns in Fall 3: H kann HALT nicht entscheiden, denn Mindestbedingung hierfür wäre ja, auf jedem Eingabewort zu terminieren.

In jedem Fall sehen wir, dass H auf dem Eingabewort yy einen Fehler macht und somit HALT nicht entscheidet. Da das für jede Turingmaschine geht, schließen wir: keine Turingmaschine kann die Sprache HALT entscheiden; sie ist unentscheidbar.

Ein Student hat am 26. Juni 2024 angemerkt, dass die Sprache

NegDiag={enc(M) | M akzeptiert enc(M) nicht}

ja eine extrem konstruierte, nicht wirklich relevante Sprache sei (da hatte er Recht). Insofern sei es auch nicht relevant, dass NegDiag unentscheidbar ist. Das ist allerdings auch nicht, was uns interessiert: unser Ziel war, zu zeigen, dass HALT unentscheidbar ist, und die Unentscheidbarkeit von NegDiag war ein Schritt auf diesem Weg. Dass HALT unentscheidbar ist, ist in der Tat relevant, denn daraus folgt (nicht direkt, aber mit ein paar technischen Tricks), dass im Prinzip jede nichttriviale Frage über das Verhalten eines Programmcodes unentscheidbar ist. Also sind auch Fragen wie "Kann das Programm abstürzen?" oder "Kann ein unautorisierter Nutzer Zugang zu XYZ erhalten?" unentscheidbar.

Das Wort Unentscheidbarkeit verwenden wir hier in seiner technischen Bedeutung, die wir definiert haben: es gibt keine Turingmaschine, die das Problem entscheidet, also auf jeder Eingabeinstanz terminiert und die richtige Antwort liefert. Es gibt also in der Tat Raum für Algorithmen, die Software untersuchen und diese gegebenenfalls verifizieren oder Fehler / Sicherheitslücken finden. Dies ist im Prinzip das sehr real existierende Forschungsfeld der Programmverifikation. Die Unentscheidbarkeit des Halteproblems impliziert nicht, dass man auf dem Feld der Programmverifikation keine Fortschritte erzielen kann; sie impliziert nur, dass es keinen ultimaten Programmverifikator bzw. Bugfinder gibt.