Warum wurde die switch-Anweisung so konzipiert, dass sie eine Pause benötigt?

Lesezeit: 8 Minuten

Benutzeravatar von EvilTeach
Böses Lehren

Gegeben sei 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 war so konzipiert. Warum wurden diese Entscheidungen getroffen? Welchen Vorteil bietet dies gegenüber einer automatischen Unterbrechungssemantik für die Blöcke? Was war die Begründung?

Viele Antworten scheinen sich auf die Fähigkeit zum Durchfallen zu konzentrieren Grund für die Forderung nach break Aussage.

Ich glaube, es war einfach ein Fehler, der größtenteils darauf zurückzuführen ist, dass es beim Entwurf von C nicht annähernd so viel Erfahrung mit der Verwendung dieser Konstrukte gab.

Peter Van der Linden argumentiert in seinem Buch “Expert C Programming”:

Wir haben die Sun-C-Compiler-Quellen analysiert, um zu sehen, wie oft der standardmäßige Fall-Through verwendet wurde. Das Sun-ANSI-C-Compiler-Frontend hat 244 switch-Anweisungen, von denen jede durchschnittlich sieben Fälle hat. Durchfall tritt nur in 3 % aller dieser Fälle auf.

Mit anderen Worten, das normale Schaltverhalten ist falsch 97% der Zeit. Es ist nicht nur in einem Compiler – im Gegenteil, wo Fall Through in dieser Analyse verwendet wurde, war es oft für Situationen, die in einem Compiler häufiger vorkommen als in anderer Software, zum Beispiel 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 Gehäusen wird so weithin als Fehler anerkannt, dass es sogar eine spezielle Kommentarkonvention gibt, die oben gezeigt wird und Lint sagt: “Dies ist wirklich einer dieser 3% der Fälle, in denen ein Durchfallen erwünscht war.”

Ich denke, es war eine gute Idee für C#, eine explizite Sprunganweisung am Ende jedes Case-Blocks zu verlangen (während immer noch mehrere Case-Labels gestapelt werden können – solange es nur einen einzigen Block von Anweisungen gibt). In C# können Sie immer noch einen Fall in einen anderen durchfallen lassen – Sie müssen den Durchbruch nur explizit machen, indem Sie mit a zum nächsten Fall springen goto.

Schade, dass Java die Gelegenheit nicht genutzt hat, um von der C-Semantik abzuweichen.

  • Tatsächlich denke ich, dass sie sich für eine einfache Implementierung entschieden haben. Einige andere Sprachen unterstützten anspruchsvollere Fälle (Bereiche, mehrere Werte, Zeichenfolgen …) möglicherweise auf Kosten der Effizienz.

    – Philho

    31. Oktober 2008 um 6:29 Uhr

  • Java wollte wahrscheinlich keine Gewohnheiten brechen und Verwirrung stiften. Für ein anderes Verhalten müssten sie eine andere Semantik verwenden. Java-Designer haben ohnehin eine Reihe von Gelegenheiten verpasst, sich von C zu lösen.

    – Philho

    31. Oktober 2008 um 6:30 Uhr

  • @PhiLho – Ich denke, Sie sind der Wahrheit mit “Einfachheit der Implementierung” wahrscheinlich am nächsten.

    – Michael Burr

    31. Oktober 2008 um 18:10 Uhr

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

    – DevinB

    13. April 2009 um 12:26 Uhr

  • selbst Pascal setzt ihre Umstellung ohne Pause um. wie konnte C-Compiler-Coder nicht darüber nachdenken @@

    – Chan Le

    28. Mai 2011 um 14:41 Uhr

dmckee --- Benutzeravatar des Ex-Moderators Kitten
dmckee — Ex-Moderator-Kätzchen

In vielerlei Hinsicht ist c nur eine saubere Schnittstelle zu Standard-Assembler-Idiomen. Beim Schreiben der Sprungtabellen-gesteuerten Ablaufsteuerung hat der Programmierer die Wahl, durch die “Steuerungsstruktur” zu fallen oder aus ihr herauszuspringen, und ein Herausspringen erfordert eine explizite Anweisung.

Also, c macht das gleiche …

  • 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 Assembler-Antimustern. 1. Hässlich: Aussagen statt Ausdrücke. “Deklaration spiegelt die Verwendung wider.” Verherrlichtes Kopieren und Einfügen als das Mechanismus für Modularität und Portabilität. Typsystem Weg zu kompliziert; ermutigt Fehler. NULL. (Fortsetzung im nächsten Kommentar.)

    – Harrison

    17. Mai 2012 um 22:30 Uhr

  • 2. Antipatterns: Bietet keine “sauberen” getaggten Unions, eine der beiden grundlegenden Datendarstellungs-Idiome der Assemblierung. (Knuth, Bd. 1, Kap. 2) Stattdessen „untagged unions“, ein nicht idiomatischer Hack. Diese Entscheidung hat die Art und Weise, wie Menschen über Daten denken, seit Jahrzehnten behindert. Und NUL-terminierte Strings sind so ziemlich die schlechteste Idee aller Zeiten.

    – Harrison

    17. Mai 2012 um 22:33 Uhr

  • @HarrisonKlaperman: Keine Methode zum Speichern von Zeichenfolgen ist perfekt. Die überwiegende Mehrheit der Probleme im Zusammenhang mit nullterminierten Zeichenfolgen würde nicht auftreten, wenn Routinen, die Zeichenfolgen akzeptieren, auch einen Parameter mit maximaler Länge akzeptieren, und würden bei Routinen auftreten, die längenmarkierte Zeichenfolgen in Puffern mit fester Größe speichern, ohne einen Puffergrößenparameter zu akzeptieren . Das Design der Switch-Anweisung, bei der Gehäuse einfach Etiketten sind, mag für moderne Augen seltsam erscheinen, aber es ist nicht schlechter als das Design des Fortran DO Schleife.

    – Superkatze

    16. Juli 2012 um 16:08 Uhr

  • 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 Stelle und führe den Code aus. Danach gehe ich nicht auf den nächsten Fall ein. Ich werde zu einer einheitlichen Adresse springen, die der Ausgang aller Fälle ist. Die Vorstellung, dass ich beim nächsten Fall in den Körper springen oder fallen würde, ist albern. Es gibt keinen Anwendungsfall für dieses Muster.

    – EvilTeach

    16. März 2014 um 19:55 Uhr


  • Während man sich heutzutage eher an cleane und sich selbst schützende Idos und Sprachfeatures gewöhnt, die einem selbst ins Knie schießen, ist dies eine Reminiszenz aus einer Zeit, in der Bytes teuer waren (C startete schon vor 1970). Wenn Ihr Code in 1024 Bytes passen muss, werden Sie unter starkem Druck stehen, Codefragmente wiederzuverwenden. Die Wiederverwendung von Code, indem an verschiedenen Einstiegspunkten mit demselben Ende begonnen wird, ist ein Mechanismus, um dies zu erreichen.

    – rpi

    12. April 2016 um 6:35 Uhr

Benutzeravatar von FlySwat
FlySwat

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);
    }
}

  • Ich liebe Duffs Gerät. So elegant und verdammt schnell.

    – dicroce

    31. Oktober 2008 um 4:51 Uhr

  • ya, aber muss es jedes Mal erscheinen, wenn es eine switch-Anweisung auf SO gibt?

    – billjamesdev

    31. Oktober 2008 um 5:27 Uhr

  • Du hast zwei schließende geschweifte Klammern übersehen ;-).

    – Toon Krijthe

    31. Oktober 2008 um 7:28 Uhr

Wenn Fälle so konzipiert waren, dass sie implizit brechen, könnten Sie keinen Fallthrough haben.

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 konzipiert wäre, dass er nach jedem Fall implizit ausbricht, hätten Sie keine Wahl. Der Schaltkastenaufbau wurde so konzipiert, dass er flexibler sein soll.

Benutzeravatar von LeopardSkinPillBoxHat
LeopardSkinPillBoxHat

Die case-Anweisungen in switch-Anweisungen sind einfach Labels.

Wenn Sie einen Wert einschalten, macht die switch-Anweisung im Wesentlichen a gehe zu auf das Etikett mit dem passenden Wert.

Das bedeutet, dass der Break notwendig ist, um nicht zum Code unter dem nächsten Label zu gelangen.

Was den Grund angeht warum Es wurde auf diese Weise implementiert – die Fall-Through-Natur einer Switch-Anweisung kann in einigen Szenarien nützlich sein. Zum Beispiel:

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;

  • Es gibt zwei große Probleme: 1) Das Vergessen break wo es gebraucht wird. 2) Wenn die Reihenfolge der case-Anweisungen geändert wurde, kann das Fallthrough dazu führen, dass der falsche Fall ausgeführt wird. Daher finde ich die Handhabung von C# viel besser (explicit goto case für Fallthrough, mit Ausnahme von Leergehäuseetiketten).

    – Daniel Rose

    28. August 2012 um 13:38 Uhr

  • @DanielRose: 1) Es gibt Möglichkeiten zu vergessen break auch in C # – am einfachsten ist es, wenn Sie keine möchten case etwas tun, aber vergessen, a hinzuzufügen break (Vielleicht haben Sie sich so in den erläuternden Kommentar vertieft oder wurden zu einer anderen Aufgabe abberufen): Die Ausführung fällt einfach auf die case unter. 2) ermutigend goto case fördert die unstrukturierte “Spaghetti”-Codierung – Sie können mit zufälligen Zyklen enden (case A: ... goto case B; case B: ... ; goto case A;), insbesondere wenn die Fälle in der Datei getrennt und schwer in Kombination zu groken sind. In C++ ist Fall-Through lokalisiert.

    – Toni Delroy

    27. Juni 2016 um 10:20 Uhr

R.. GitHub STOP HELPING ICEs Benutzeravatar
R.. GitHub HÖREN SIE AUF, ICE ZU HELFEN

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 */
}

Denken Sie an die case Stichwort als goto Label und es kommt viel natürlicher.

  • Es gibt zwei große Probleme: 1) Das Vergessen break wo es gebraucht wird. 2) Wenn die Reihenfolge der case-Anweisungen geändert wurde, kann das Fallthrough dazu führen, dass der falsche Fall ausgeführt wird. Daher finde ich die Handhabung von C# viel besser (explicit goto case für Fallthrough, mit Ausnahme von Leergehäuseetiketten).

    – Daniel Rose

    28. August 2012 um 13:38 Uhr

  • @DanielRose: 1) Es gibt Möglichkeiten zu vergessen break auch in C # – am einfachsten ist es, wenn Sie keine möchten case etwas tun, aber vergessen, a hinzuzufügen break (Vielleicht haben Sie sich so in den erläuternden Kommentar vertieft oder wurden zu einer anderen Aufgabe abberufen): Die Ausführung fällt einfach auf die case unter. 2) ermutigend goto case fördert die unstrukturierte “Spaghetti”-Codierung – Sie können mit zufälligen Zyklen enden (case A: ... goto case B; case B: ... ; goto case A;), insbesondere wenn die Fälle in der Datei getrennt und schwer in Kombination zu groken sind. In C++ ist Fall-Through lokalisiert.

    – Toni Delroy

    27. Juni 2016 um 10:20 Uhr

Benutzeravatar von Greg Rogers
Greg Rogers

Es eliminiert Codeduplizierung, 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 umbrechen oder nicht, gibt es sowieso keinen Overhead für Fall-Through-Fälle, also warum nicht zulassen, da sie in bestimmten Fällen erhebliche Vorteile bieten.

1424260cookie-checkWarum wurde die switch-Anweisung so konzipiert, dass sie eine Pause benötigt?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy