Warum sollte die switch-Anweisung eine Pause benötigen?


139

Gegeben eine einfache switch-Anweisung

switch (int)
{
    case 1 :
    {
        printf("1\n");
        break;
    }

    case 2 : 
    {
        printf("2\n");
    }

    case 3 : 
    {
        printf("3\n");
    }
}

Das Fehlen einer break-Anweisung in Fall 2 impliziert, dass die Ausführung innerhalb des Codes für Fall 3 fortgesetzt wird. Dies ist kein Unfall. es wurde so entworfen. Warum wurden diese Entscheidungen getroffen? Welchen Vorteil bietet dies gegenüber einer automatischen Unterbrechungssemantik für die Blöcke? Was war die Begründung?

Antworten:


150

Viele Antworten scheinen sich auf die Fähigkeit zu konzentrieren, als Grund für die Forderung nach dem Durchfallen zu fallenbreak Aussage .

Ich glaube, es war einfach ein Fehler, vor allem, weil bei der Entwicklung von C nicht annähernd so viel Erfahrung mit der Verwendung dieser Konstrukte vorhanden war.

Peter Van der Linden macht den Fall in seinem Buch "Expert C Programming":

Wir haben die Sun C-Compilerquellen analysiert, um festzustellen, wie oft der Standard-Fall-Through verwendet wurde. Das Sun ANSI C-Compiler-Frontend verfügt über 244 switch-Anweisungen, von denen jede durchschnittlich sieben Fälle aufweist. In nur 3% aller dieser Fälle tritt ein Durchfall auf.

Mit anderen Worten, das normale Schaltverhalten ist in 97% der Fälle falsch . Es ist nicht nur in einem Compiler - im Gegenteil, wo in dieser Analyse Fall Through verwendet wurde, war es häufig für Situationen, die in einem Compiler häufiger auftreten als in anderer Software, beispielsweise beim Kompilieren von Operatoren, die entweder einen oder zwei Operanden haben können ::

switch (operator->num_of_operands) {
    case 2: process_operand( operator->operand_2);
              /* FALLTHRU */

    case 1: process_operand( operator->operand_1);
    break;
}

Das Durchfallen von Fällen ist so weit verbreitet, dass es sogar eine spezielle Kommentarkonvention gibt, die oben zeigt, dass "dies wirklich einer der 3% der Fälle ist, in denen ein Durchfallen gewünscht wurde".

Ich denke, es war eine gute Idee für C #, am Ende jedes Fallblocks eine explizite Sprunganweisung zu verlangen (während immer noch mehrere Fallbezeichnungen gestapelt werden können - solange es nur einen einzigen Anweisungsblock gibt). In C # kann immer noch ein Fall in einen anderen fallen - Sie müssen nur den Fall explizit machen, indem Sie mit a zum nächsten Fall springengoto .

Es ist schade, dass Java die Gelegenheit nicht genutzt hat, um von der C-Semantik abzubrechen.


Ich denke, sie haben sich für eine einfache Implementierung entschieden. Einige andere Sprachen unterstützten komplexere Fälle (Bereiche, mehrere Werte, Zeichenfolgen ...) auf Kosten der Effizienz.
PhiLho

Java wollte wahrscheinlich nicht die Gewohnheiten brechen und Verwirrung stiften. Für ein anderes Verhalten müssten sie eine andere Semantik verwenden. Java-Designer haben ohnehin eine Reihe von Möglichkeiten verloren, sich von C zu lösen.
PhiLho

@PhiLho - Ich denke, Sie sind der Wahrheit mit "Einfachheit der Implementierung" wahrscheinlich am nächsten.
Michael Burr

Wenn Sie explizite Sprünge über GOTO verwenden, ist es nicht genauso produktiv, nur eine Reihe von IF-Anweisungen zu verwenden?
DevinB

3
Sogar Pascal implementiert ihren Schalter ohne Unterbrechung. Wie konnte C Compiler-Codierer nicht darüber nachdenken @@
Chan Le

30

In vielerlei Hinsicht ist c nur eine saubere Schnittstelle zu Standard-Assembly-Redewendungen. Beim Schreiben einer von der Sprungtabelle gesteuerten Flusssteuerung hat der Programmierer die Wahl, durch die "Steuerstruktur" zu fallen oder aus dieser herauszuspringen, und ein Herausspringen erfordert eine explizite Anweisung.

Also macht c dasselbe ...


1
Ich weiß, dass viele Leute das sagen, aber ich denke, es ist nicht das vollständige Bild; C ist auch oft eine hässliche Schnittstelle zu Antimustern für Baugruppen. 1. Hässlich: Aussagen statt Ausdrücke. "Die Erklärung spiegelt die Verwendung wider." Verherrlicht Copy-Paste als der Mechanismus für die Modularität und Portabilität. Typ System viel zu kompliziert; ermutigt zu Fehlern. NULL. (Fortsetzung im nächsten Kommentar.)
Harrison

2. Antipatterns: Bietet keine "sauberen" Gewerkschaften mit Tags, eine der beiden grundlegenden Redewendungen für die Datendarstellung von Assemblierungen. (Knuth, Band 1, Kapitel 2) Stattdessen "Gewerkschaften ohne Tags", ein nicht-idiomatischer Hack. Diese Wahl hat die Art und Weise, wie Menschen über Daten denken, seit Jahrzehnten behindert. Und NUL-terminierte Saiten sind fast die schlechteste Idee, die es je gab.
Harrison

@ HarrisonKlaperman: Keine Methode zum Speichern von Strings ist perfekt. Die überwiegende Mehrheit der Probleme, die mit nullterminierten Zeichenfolgen verbunden sind, würde nicht auftreten, wenn Routinen, die Zeichenfolgen akzeptieren, auch einen Parameter maximaler Länge akzeptieren, und würden bei Routinen auftreten, die Zeichenfolgen mit Längenmarkierung in Puffern fester Größe speichern, ohne einen Parameter Puffergröße zu akzeptieren . Das Design der switch-Anweisung, bei der Fälle einfach Beschriftungen sind, mag modernen Augen seltsam erscheinen, aber es ist nicht schlechter als das Design der Fortran- DOSchleife.
Supercat

Wenn ich mich entscheide, eine Sprungtabelle in Assembly zu schreiben, nehme ich den Fallwert und verwandle ihn auf magische Weise in den Index der Sprungtabelle, springe zu dieser Position und führe den Code aus. Danach werde ich nicht mehr in den nächsten Fall springen. Ich werde zu einer einheitlichen Adresse springen, die den Ausgang aller Fälle darstellt. Die Idee, dass ich in den Körper des nächsten Falles springen oder fallen würde, ist dumm. Es gibt keinen Anwendungsfall für dieses Muster.
EvilTeach

2
Während Menschen heutzutage eher daran gewöhnt sind, Idos und Sprachmerkmale zu bereinigen und selbst zu schützen, die verhindern, dass sie in den Fuß schießen, ist dies eine Reminiszenz aus einem Bereich, in dem Bytes teuer waren (C begann vor 1970). Wenn Ihr Code innerhalb von 1024 Byte passen muss, treten große Probleme bei der Wiederverwendung von Codefragmenten auf. Die Wiederverwendung von Code an verschiedenen Einstiegspunkten, die dasselbe Ziel haben, ist ein Mechanismus, um dies zu erreichen.
rpy

23

Um Duffs Gerät zu implementieren, natürlich:

dsend(to, from, count)
char *to, *from;
int count;
{
    int n = (count + 7) / 8;
    switch (count % 8) {
    case 0: do { *to = *from++;
    case 7:      *to = *from++;
    case 6:      *to = *from++;
    case 5:      *to = *from++;
    case 4:      *to = *from++;
    case 3:      *to = *from++;
    case 2:      *to = *from++;
    case 1:      *to = *from++;
               } while (--n > 0);
    }
}

3
Ich liebe Duffs Gerät. So elegant und schnell böse.
Dicroce

7
Ja, aber muss es jedes Mal angezeigt werden, wenn eine switch-Anweisung für SO vorliegt?
Billjamesdev

Du hast zwei schließende geschweifte Klammern verpasst ;-).
Toon Krijthe

18

Wenn Fälle so konzipiert wären, dass sie implizit brechen, könnten Sie nicht durchfallen.

case 0:
case 1:
case 2:
    // all do the same thing.
    break;
case 3:
case 4:
    // do something different.
    break;
default:
    // something else entirely.

Wenn der Schalter so ausgelegt wäre, dass er nach jedem Fall implizit ausbricht, hätten Sie keine Wahl. Die Schaltkastenstruktur wurde so gestaltet, dass sie flexibler ist.


12
Sie können sich einen Schalter vorstellen, der implizit unterbrochen wird, aber das Schlüsselwort "fallthrough" hat. Umständlich, aber praktikabel.
dmckee --- Ex-Moderator Kätzchen

Wie wäre das besser? Ich würde mir vorstellen, dass eine case-Anweisung häufiger so funktioniert als "Codeblock pro Fall" ... das ist ein if / then / else-Block.
Billjamesdev

Ich würde hinzufügen, dass in der Frage die Scope-Enclosures {} in jedem Fall die Verwirrung verstärken, da es wie der Stil einer "while" -Anweisung aussieht.
Matthew Smith

1
@ Bill: Es wäre schlimmer, denke ich, aber es würde die von Mike B vorgebrachte Beschwerde ansprechen: Dieser Sturz (abgesehen von mehreren Fällen gleich) ist ein seltenes Ereignis und sollte nicht das Standardverhalten sein.
dmckee --- Ex-Moderator Kätzchen

1
Ich habe die Zahnspange der Kürze halber weggelassen, mit mehreren Aussagen sollten sie da sein. Ich bin damit einverstanden, dass Fallthrough nur verwendet werden sollte, wenn die Fälle genau gleich sind. Es ist höllisch verwirrend, wenn Fälle mit Fallthrough auf früheren Fällen aufbauen.
Bill the Lizard

15

Die case-Anweisungen in einer switch-Anweisung sind einfach Bezeichnungen.

Wenn Sie einen Wert einschalten, führt die switch-Anweisung im Wesentlichen ein goto aus auf das Etikett mit dem passenden Wert.

Dies bedeutet, dass die Unterbrechung erforderlich ist, um zu vermeiden, dass der Code unter dem nächsten Etikett angezeigt wird.

Was den Grund betrifft, warum es auf diese Weise implementiert wurde, ist, dass der Fall-Through-Charakter einer switch-Anweisung in einigen Szenarien nützlich sein kann. Beispielsweise:

case optionA:
    // optionA needs to do its own thing, and also B's thing.
    // Fall-through to optionB afterwards.
    // Its behaviour is a superset of B's.
case optionB:
    // optionB needs to do its own thing
    // Its behaviour is a subset of A's.
    break;
case optionC:
    // optionC is quite independent so it does its own thing.
    break;

2
Es gibt zwei große Probleme: 1) Vergessen, breakwo es gebraucht wird. 2) Wenn die Reihenfolge der case-Anweisungen geändert wird, kann der Fallthrough dazu führen, dass der falsche Fall ausgeführt wird. Daher finde ich die Handhabung von C # viel besser (explizit goto casefür Fallthrough, außer für leere Falletiketten).
Daniel Rose

@DanielRose: 1) Es gibt Möglichkeiten, auch breakin C # zu vergessen - am einfachsten ist es, wenn Sie nicht möchten, dass a caseetwas tut, sondern vergessen, a hinzuzufügenbreak (vielleicht haben Sie sich so in den erklärenden Kommentar vertieft oder wurden bei einigen abgerufen andere Aufgabe): Die Ausführung wird nur bis zum caseFolgenden durchgeführt. 2) Ermutigen goto caseheißt unstrukturierte "Spaghetti" -Codierung fördern - es kann zu versehentlichen Zyklen kommen ( case A: ... goto case B; case B: ... ; goto case A;), insbesondere wenn die Fälle in der Datei getrennt und in Kombination schwer zu erfassen sind. In C ++ ist der Durchfall lokalisiert.
Tony Delroy

8

Um Dinge zuzulassen wie:

switch(foo) {
case 1:
    /* stuff for case 1 only */
    if (0) {
case 2:
    /* stuff for case 2 only */
    }
    /* stuff for cases 1 and 2 */
case 3:
    /* stuff for cases 1, 2, and 3 */
}

Stellen Sie sich das caseSchlüsselwort als gotoLabel vor und es kommt viel natürlicher.


8
Das if(0)am Ende des ersten Falles ist böse und sollte nur verwendet werden, wenn das Ziel darin besteht, den Code zu verschleiern.
Daniel Rose

11
Diese ganze Antwort war eine Übung, böse zu sein, denke ich. :-)
R .. GitHub STOP HELPING ICE

3

Es verhindert die Duplizierung von Code, wenn mehrere Fälle denselben Code (oder denselben Code nacheinander) ausführen müssen.

Da es auf der Ebene der Assemblersprache egal ist, ob Sie zwischen den einzelnen Fällen wechseln oder nicht, gibt es sowieso keinen Overhead für Fall-Through-Fälle. Warum also nicht zulassen, da sie in bestimmten Fällen erhebliche Vorteile bieten?


2

Ich bin zufällig auf einen Fall gestoßen, bei dem Strukturen Werte in Vektoren zugewiesen wurden: Dies musste so erfolgen, dass der Rest der Mitglieder in verbleiben würde, wenn der Datenvektor kürzer als die Anzahl der Datenelemente in der Struktur wäre ihren Standardwert. In diesem Fall war das Weglassen breaksehr nützlich.

switch (nShorts)
{
case 4: frame.leadV1    = shortArray[3];
case 3: frame.leadIII   = shortArray[2];
case 2: frame.leadII    = shortArray[1];
case 1: frame.leadI     = shortArray[0]; break;
default: TS_ASSERT(false);
}

0

Wie viele hier angegeben haben, soll ein einzelner Codeblock für mehrere Fälle funktionieren. Dies sollte bei Ihren switch-Anweisungen häufiger vorkommen als bei dem in Ihrem Beispiel angegebenen "Codeblock pro Fall".

Wenn Sie einen Codeblock pro Fall ohne Durchfall haben, sollten Sie möglicherweise einen if-elseif-else-Block verwenden, da dies angemessener erscheint.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.