Um die Antworten hier zu ergänzen, denke ich, dass es sich lohnt, die entgegengesetzte Frage in Verbindung damit zu betrachten, nämlich. Warum hat C überhaupt einen Durchfall zugelassen?
Jede Programmiersprache dient natürlich zwei Zielen:
- Geben Sie dem Computer Anweisungen.
- Hinterlassen Sie eine Aufzeichnung der Absichten des Programmierers.
Die Schaffung einer Programmiersprache ist daher ein Gleichgewicht zwischen der Frage, wie diese beiden Ziele am besten erreicht werden können. Einerseits ist der Kompilierungs- oder Interpretationsprozess umso effizienter, zuverlässiger und zuverlässiger, je einfacher es ist, sich in Computeranweisungen umzuwandeln (unabhängig davon, ob es sich um Maschinencode, Bytecode wie IL handelt oder ob die Anweisungen bei der Ausführung interpretiert werden) kompakte Ausgabe. Im Extremfall führt dieses Ziel dazu, dass wir nur in Assembly, IL oder sogar in rohen Op-Codes schreiben, da die einfachste Kompilierung darin besteht, dass überhaupt keine Kompilierung erfolgt.
Umgekehrt ist das Programm sowohl beim Schreiben als auch während der Wartung verständlicher, je mehr die Sprache die Absicht des Programmierers zum Ausdruck bringt und nicht die Mittel, die zu diesem Zweck eingesetzt werden.
Jetzt switch
hätte man es immer kompilieren können, indem man es in die äquivalente Kette von if-else
Blöcken oder ähnlichem konvertierte , aber es wurde so konzipiert, dass es die Kompilierung in ein bestimmtes gemeinsames Baugruppenmuster ermöglicht, in dem man einen Wert nimmt und einen Versatz daraus berechnet (sei es durch Nachschlagen einer Tabelle) indiziert durch einen perfekten Hash des Wertes oder durch tatsächliche Arithmetik des Wertes *). An dieser Stelle ist anzumerken, dass die C # -Kompilierung heute manchmal switch
zum Äquivalent if-else
wird und manchmal einen Hash-basierten Sprungansatz verwendet (und ebenfalls mit C, C ++ und anderen Sprachen mit vergleichbarer Syntax).
In diesem Fall gibt es zwei gute Gründe, einen Durchfall zuzulassen:
Es passiert sowieso nur natürlich: Wenn Sie eine Sprungtabelle in einen Befehlssatz einbauen und einer der früheren Befehlsstapel keine Art von Sprung oder Rückgabe enthält, wird die Ausführung natürlich auf den nächsten Stapel fortgesetzt. Fall-Through zuzulassen war das, was "einfach passieren" würde, wenn Sie das- switch
using C in eine Sprungtabelle verwandeln würden - unter Verwendung von Maschinencode.
Codierer, die in Assembly geschrieben haben, waren bereits an das Äquivalent gewöhnt: Beim Schreiben einer Sprungtabelle von Hand in Assembly mussten sie berücksichtigen, ob ein bestimmter Codeblock mit einer Rückgabe, einem Sprung außerhalb der Tabelle enden oder einfach fortfahren würde zum nächsten Block. Daher war es auch break
für den Codierer "natürlich" , wenn der Codierer bei Bedarf eine explizite Option hinzufügte .
Zu dieser Zeit war es daher ein vernünftiger Versuch, die beiden Ziele einer Computersprache in Einklang zu bringen, da sie sich sowohl auf den erzeugten Maschinencode als auch auf die Ausdruckskraft des Quellcodes bezieht.
Vier Jahrzehnte später sind die Dinge aus mehreren Gründen nicht ganz die gleichen:
- Codierer in C haben heute möglicherweise wenig oder keine Montageerfahrung. Codierer in vielen anderen Sprachen im C-Stil sind noch weniger wahrscheinlich (insbesondere Javascript!). Ein Konzept von "was Menschen von der Montage gewohnt sind" ist nicht mehr relevant.
- Verbesserungen bei den Optimierungen bedeuten, dass die Wahrscheinlichkeit höher ist, dass sie
switch
entweder umgesetzt werden, if-else
weil sie als der wahrscheinlich effizienteste Ansatz angesehen wurden, oder dass sie zu einer besonders esoterischen Variante des Sprungtabellenansatzes werden. Die Zuordnung zwischen den Ansätzen auf höherer und niedrigerer Ebene ist nicht mehr so stark wie früher.
- Die Erfahrung hat gezeigt, dass Fall-Through eher der Minderheitsfall als die Norm ist (eine Studie des Sun-Compilers ergab, dass 3% der
switch
Blöcke einen anderen Fall-Through als mehrere Labels auf demselben Block verwendeten, und es wurde angenommen, dass die Verwendung von Fall hier bedeutete, dass diese 3% tatsächlich viel höher als normal waren). Die Sprache, wie sie studiert wird, macht das Ungewöhnliche leichter zugänglich als das Übliche.
- Die Erfahrung hat gezeigt, dass das Durchfallen sowohl in Fällen, in denen es versehentlich durchgeführt wird, als auch in Fällen, in denen jemand, der den Code verwaltet, den korrekten Durchfall übersieht, zu Problemen führt. Letzteres ist eine subtile Ergänzung zu den mit Fall-Through verbundenen Fehlern, denn selbst wenn Ihr Code vollkommen fehlerfrei ist, kann Ihr Fall-Through dennoch Probleme verursachen.
Beachten Sie im Zusammenhang mit diesen beiden letzten Punkten das folgende Zitat aus der aktuellen Ausgabe von K & R:
Das Durchfallen von einem Fall zum anderen ist nicht robust und neigt zum Zerfall, wenn das Programm geändert wird. Mit Ausnahme mehrerer Beschriftungen für eine einzelne Berechnung sollten Fall-Throughs sparsam verwendet und kommentiert werden.
Machen Sie aus gutem Grund nach dem letzten Fall eine Pause (hier die Standardeinstellung), obwohl dies logisch nicht erforderlich ist. Eines Tages, wenn am Ende ein weiterer Fall hinzugefügt wird, werden Sie durch diese defensive Programmierung gerettet.
Aus dem Maul des Pferdes ist ein Durchfall in C problematisch. Es wird als gute Praxis angesehen, Durchfälle immer mit Kommentaren zu dokumentieren. Dies ist eine Anwendung des allgemeinen Prinzips, dass man dokumentieren sollte, wo man etwas Ungewöhnliches tut, da dies die spätere Prüfung des Codes auslöst und / oder Ihren Code so aussehen lässt hat einen Anfängerfehler, wenn er tatsächlich korrekt ist.
Und wenn Sie darüber nachdenken, codieren Sie wie folgt:
switch(x)
{
case 1:
foo();
/* FALLTHRU */
case 2:
bar();
break;
}
Bin das Hinzufügen etwas den Durchfall explizit im Code zu machen, es ist einfach nicht etwas , das erkannt werden kann (oder deren Abwesenheit nachgewiesen werden kann) durch den Compiler.
Die Tatsache, dass on mit Fall-Through in C # explizit angegeben werden muss, bedeutet für Personen, die ohnehin gut in anderen C-Sprachen geschrieben haben, keine Strafe, da sie in ihren Fall-Throughs bereits explizit wären. †
Schließlich ist die Verwendung von goto
hier bereits eine Norm aus C und anderen solchen Sprachen:
switch(x)
{
case 0:
case 1:
case 2:
foo();
goto below_six;
case 3:
bar();
goto below_six;
case 4:
baz();
/* FALLTHRU */
case 5:
below_six:
qux();
break;
default:
quux();
}
In einem solchen Fall, in dem ein Block in den Code aufgenommen werden soll, der für einen anderen Wert als den ausgeführt wird, der einen zum vorhergehenden Block bringt, müssen wir ihn bereits verwenden goto
. (Natürlich gibt es Mittel und Wege, dies mit unterschiedlichen Bedingungen zu vermeiden, aber das gilt für fast alles, was mit dieser Frage zu tun hat). Als solches baute C # auf der ohnehin normalen Art und Weise auf, mit einer Situation umzugehen, in der wir mehr als einen Codeblock in a treffen möchten switch
, und verallgemeinerte ihn nur, um auch den Durchfall abzudecken. Es machte auch beide Fälle bequemer und selbstdokumentierender, da wir in C ein neues Etikett hinzufügen müssen, das case
aber in C # als Etikett verwenden können. In C # können wir das below_six
Etikett entfernen und verwenden, goto case 5
was klarer ist, was wir tun. (Wir müssten auch hinzufügenbreak
für die default
, die ich weggelassen habe, nur um den obigen C-Code eindeutig nicht C # -Code zu machen).
Zusammenfassend also:
- C # bezieht sich nicht mehr so direkt auf die nicht optimierte Compilerausgabe wie C-Code vor 40 Jahren (und C heutzutage auch nicht mehr), was eine der Inspirationen für Fall-Through irrelevant macht.
- C # bleibt mit C kompatibel, da es nicht nur implizit ist
break
, sondern auch das Erlernen der Sprache durch Personen erleichtert, die mit ähnlichen Sprachen vertraut sind, und das Portieren erleichtert.
- C # entfernt eine mögliche Quelle von Fehlern oder missverstandenem Code, von dem in den letzten vier Jahrzehnten dokumentiert wurde, dass er Probleme verursacht.
- C # macht vorhandene Best Practices mit C (Document Fall Through) für den Compiler durchsetzbar.
- C # macht den ungewöhnlichen Fall zu dem mit expliziterem Code, den üblichen Fall zu dem mit dem Code, den man nur automatisch schreibt.
- C # verwendet den gleichen
goto
Ansatz, um denselben Block von verschiedenen case
Bezeichnungen zu treffen , wie er in C verwendet wird. Es verallgemeinert ihn nur auf einige andere Fälle.
- C # macht diesen
goto
basierten Ansatz bequemer und klarer als in C, indem case
Anweisungen als Beschriftungen fungieren.
Alles in allem eine ziemlich vernünftige Designentscheidung
* Einige Formen von BASIC würden es einem ermöglichen, solche Dinge zu tun, GOTO (x AND 7) * 50 + 240
die zwar spröde und daher ein besonders überzeugender Fall für ein Verbot sind goto
, aber dazu dienen, ein höhersprachiges Äquivalent der Art und Weise zu zeigen, auf der Code auf niedrigerer Ebene einen Sprung basierend machen kann Arithmetik auf einen Wert, der viel vernünftiger ist, wenn er das Ergebnis einer Kompilierung ist, als etwas, das manuell gepflegt werden muss. Insbesondere Implementierungen von Duff's Device eignen sich gut für den entsprechenden Maschinencode oder IL, da jeder Anweisungsblock häufig dieselbe Länge hat, ohne dass nop
Füllstoffe hinzugefügt werden müssen.
† Duffs Gerät wird hier als vernünftige Ausnahme wieder angezeigt. Die Tatsache, dass es mit diesem und ähnlichen Mustern zu einer Wiederholung von Operationen kommt, dient dazu, die Verwendung von Durchfall auch ohne einen expliziten Kommentar zu diesem Effekt relativ deutlich zu machen.