Das Dangling Else-Problem ist eine Mehrdeutigkeit in der Code-Syntaxspezifikation, bei der möglicherweise unklar ist, ob und was sonst zu welchem if gehört.
Das einfachste und klassischste Beispiel:
if(conditionA)
if(conditionB)
doFoo();
else
doBar();
Es ist unklar, wer die Besonderheiten der Sprachspezifikation nicht auswendig kennt, der if
bekommt das else
(und dieser spezielle Codeausschnitt ist in einem halben Dutzend Sprachen gültig, kann aber in jedem anders abschneiden).
Das Dangling Else-Konstrukt stellt ein potenzielles Problem für scannerlose Parserimplementierungen dar, da die Strategie darin besteht, den Dateistream zeichenweise zu verschlucken, bis der Parser erkennt, dass er genug Token hat (Zusammenfassung in die zu kompilierende Assembly- oder Zwischensprache). . Dadurch kann der Parser den minimalen Status beibehalten. Sobald er glaubt, dass er über genügend Informationen verfügt, um die Token zu schreiben, die er in die Datei geparst hat, wird er dies tun. Das ist das Endziel eines scannerlosen Parsers. Schnelle, einfache und leichte Zusammenstellung.
Unter der Annahme, dass Zeilenumbrüche und Leerzeichen vor oder nach der Interpunktion bedeutungslos sind (wie in den meisten Sprachen im C-Stil), würde diese Anweisung dem Compiler so erscheinen:
if(conditionA)if(conditionB)doFoo();else doBar;
Perfekt zum Parsen mit einem Computer, sehen wir uns das an. Ich bekomme jeweils einen Charakter, bis ich:
if(conditionA)
Oh, ich weiß, was das bedeutet (in C #), es bedeutet " push
conditionA auf dem Eval-Stack und dann call brfalse
, um nach dem nächsten Semikolon zur Anweisung zu springen, wenn es nicht wahr ist". Im Moment sehe ich kein Semikolon. Daher setze ich meinen Sprungversatz zunächst auf das nächste Leerzeichen nach dieser Anweisung und erhöhe diesen Versatz, wenn ich weitere Anweisungen einfüge, bis ich ein Semikolon sehe. Weiter zu analysieren ...
if(conditionB)
OK, dies wird zu einem ähnlichen Paar von IL-Operationen analysiert und sofort nach der Anweisung, die ich gerade analysiert habe, ausgeführt. Ich sehe kein Semikolon, daher erhöhe ich den Sprungversatz meiner vorherigen Anweisung um die Länge meiner beiden Befehle (einer für den Push und einer für den Break) und halte Ausschau.
doFoo();
Ok, das ist ganz einfach. Das ist " call
doFoo". Und ist das ein Semikolon, das ich sehe? Nun, das ist großartig, das ist das Ende der Reihe. Ich werde die Sprungversätze meiner beiden Blöcke um die Länge dieser beiden Befehle erhöhen und vergessen, dass es mich jemals interessiert hat. OK, mach weiter ...
else
... Äh-oh. Das ist nicht so einfach, wie es aussah. OK, ich habe vergessen , was ich nur tun, sondern ein else
Mittel gibt es eine bedingte break - Anweisung irgendwo , dass ich schon gesehen habe, mich zurückblicken lassen ... yep, gibt es, brfalse
direkt nach schiebe ich einige „conditionB“ auf der Stapel, was auch immer das war. OK, jetzt brauche ich eine unbedingte break
als nächste Aussage. Die Aussage, die danach kommt, ist definitiv das Ziel meiner bedingten Pause. Ich werde also sicherstellen, dass ich sie richtig einsetze, und die bedingungslose Pause, die ich einsetze, inkrementieren. Weiter ...
doBar();
Das ist leicht. " call
doBar". Und es gibt ein Semikolon, und ich habe keine Klammern gesehen. Also, das Unbedingte break
sollte zur nächsten Aussage springen, was auch immer es ist, und ich kann vergessen, dass es mich jemals kümmerte.
Also, was haben wir ... (Anmerkung: Es ist 22:00 Uhr und ich habe keine Lust, Bit-Offsets in hexadezimale Werte umzuwandeln oder die vollständige IL-Shell einer Funktion mit diesen Befehlen auszufüllen. Das ist also nur Pseudo-IL Verwenden von Zeilennummern, bei denen normalerweise Byte-Offsets auftreten würden):
ldarg.1 //conditionA
brfalse <line 6> //jumps to "break"
ldarg.2 //conditionB
brfalse <line 7> //jumps to "call doBar"
call doFoo
break <line 8> //jumps beyond statement in scope
call doBar
<line 8 is here>
Nun, das funktioniert tatsächlich richtig, WENN die Regel (wie in den meisten C-Stil-Sprachen) lautet, dass das else
mit dem nächsten geht if
. Eingezogen, um der Verschachtelung der Ausführung zu folgen, wird dies folgendermaßen ausgeführt: Wenn conditionA false ist, wird der gesamte Rest des Snippets übersprungen:
if(conditionA)
if(conditionB)
doFoo();
else
doBar();
... dies geschieht jedoch durch Zufall, da der mit der äußeren if
Anweisung verbundene Umbruch zu der break
Anweisung am Ende der inneren Anweisung springt if
, die den Ausführungszeiger über die gesamte Anweisung hinausführt. Es ist ein zusätzlicher unnötiger Sprung, und wenn dieses Beispiel komplexer wäre, könnte es nicht mehr funktionieren, wenn es auf diese Weise analysiert und mit einem Token versehen wird.
Was ist auch, wenn die Sprachspezifikation besagt, dass ein Dangling else
zum ersten if
gehört und wenn BedingungA falsch ist, dann wird doBar ausgeführt, während, wenn BedingungA wahr ist, aber nicht BedingungB, dann nichts passiert, so?
if(conditionA)
if(conditionB)
doFoo();
else
doBar();
Der Parser hatte vergessen, dass es den ersten if
überhaupt gab, und so würde dieser einfache Parser-Algorithmus keinen korrekten Code erzeugen, geschweige denn effizienten Code.
Jetzt könnte der Parser klug genug sein, um sich an die if
s und else
s zu erinnern, die er für eine längere Zeit hat. Wenn jedoch die Sprachspezifikation sagt , dass else
nach zwei if
s Übereinstimmungen eine einzelne mit der ersten übereinstimmt if
, führt dies zu einem Problem mit zwei if
s mit übereinstimmenden else
s:
if(conditionA)
if(conditionB)
doFoo();
else
doBar();
else
doBaz();
Der Parser sieht das erste else
, stimmt mit dem ersten überein if
, sieht dann das zweite und gerät in Panik "Was zum Teufel habe ich nochmal gemacht". Zu diesem Zeitpunkt hat der Parser ziemlich viel Code in einem veränderlichen Zustand, den er viel lieber schon in den Ausgabedateistream gepusht hätte.
Es gibt Lösungen für all diese Probleme und Was-wäre-wenn. Aber entweder der Code, der so intelligent sein musste, erhöht die Komplexität des Parser-Algorithmus, oder die Sprachspezifikation, die es dem Parser ermöglicht, so dumm zu sein, erhöht die Ausführlichkeit des Sprachquellcodes, indem er beispielsweise terminierende Anweisungen wie end if
oder geschachtelte Klammern erfordert blockiert, wenn die if
Anweisung eine hat else
(beide werden üblicherweise in anderen Sprachstilen verwendet).
Dies ist nur ein einfaches Beispiel für ein paar if
Aussagen und zeigt alle Entscheidungen, die der Compiler treffen musste, und wo es ohnehin sehr leicht durcheinander gekommen sein könnte. Dies ist das Detail hinter dieser harmlosen Aussage von Wikipedia in Ihrer Frage.