Soll if (0) zum Überspringen eines Falls in einem Switch funktionieren?

117

Ich habe eine Situation, in der ich möchte, dass zwei Fälle in einer C ++ - switch-Anweisung beide zu einem dritten Fall durchfallen. Insbesondere würde der zweite Fall bis zum dritten Fall durchfallen , und der erste Fall würde auch bis zum dritten Fall durchfallen, ohne den zweiten Fall zu durchlaufen.

Ich hatte eine blöde Idee, habe es versucht und es hat funktioniert! Ich wickelte den zweiten Fall in einem if (0) {... }. Es sieht aus wie das:

#ifdef __cplusplus
#  include <cstdio>
#else
#  include <stdio.h>
#endif

int main(void) {
    for (int i = 0; i < 3; i++) {
        printf("%d: ", i);
        switch (i) {
        case 0:
            putchar('a');
            // @fallthrough@
            if (0) {        // fall past all of case 1 (!)
        case 1:
            putchar('b');
            // @fallthrough@
            }
        case 2:
            putchar('c');
            break;
        }
        putchar('\n');
    }
    return 0;
}

Wenn ich es ausführe, erhalte ich die gewünschte Ausgabe:

0: ac
1: bc
2: c

Ich habe es sowohl in C als auch in C ++ versucht (beide mit Clang), und es hat das Gleiche getan.

Meine Fragen sind: Ist das C / C ++ gültig? Soll es tun, was es tut?

Mark Adler
quelle
33
Ja, dies ist gültig und funktioniert aus den gleichen Gründen, aus denen Duffs Gerät funktioniert.
22.
41
Beachten Sie, dass Code wie dieser Sie in einer Umgebung, in der es nur ein wenig um Lesbarkeit und Wartbarkeit geht, aus jeder Code-Komplettlösung herausholt.
Andrew Henle
16
Das ist schrecklich. Noch schrecklicher als Duffs Gerät, wohlgemerkt. Im Zusammenhang habe ich auch kürzlich so etwas gesehen switch(x) { case A: case B: do_this(); if(x == B) also_do_that(); ... }. Das war auch, IMO, schrecklich. Bitte schreiben Sie solche Dinge einfach so aus, als ob Anweisungen, auch wenn dies bedeutet, dass Sie eine Zeile an zwei Stellen wiederholen müssen. Verwenden Sie Funktionen und Variablen (und Dokumentation!), Um das Risiko einer versehentlichen späteren Aktualisierung an nur einer Stelle zu verringern.
ilkkachu
48
:-) Für diejenigen, die durch das Betrachten dieses Codes verletzt oder verstümmelt wurden, habe ich nicht gesagt, dass es eine gute Idee ist. Tatsächlich sagte ich, es sei eine blöde Idee.
Mark Adler
4
Beachten Sie, dass Konstruktionen in
solchen

Antworten:

57

Ja, das ist erlaubt und macht was du willst. Für eine switchAnweisung lautet der C ++ - Standard :

Groß- und Kleinschreibung an sich ändern nicht den Kontrollfluss, der über solche Beschriftungen hinweg ungehindert weitergeht. Informationen zum Verlassen eines Schalters finden Sie unter Pause.

[Anmerkung 1: Normalerweise ist die Unteranweisung, die Gegenstand eines Wechsels ist, zusammengesetzt, und Groß- und Kleinschreibung und Standardbezeichnungen werden in den Anweisungen der obersten Ebene angezeigt, die in der (zusammengesetzten) Unteranweisung enthalten sind. Dies ist jedoch nicht erforderlich. Deklarationen können in der Unteranweisung einer switch-Anweisung erscheinen. - Endnote]

Wenn die ifAnweisung ausgewertet wird, wird der Kontrollfluss ifunabhängig von den dazwischen liegenden Fallbezeichnungen gemäß den Regeln einer Anweisung fortgesetzt.

cigien
quelle
13
In einem speziellen Fall kann man in Boost.Coroutines nach einer Reihe von Makros suchen , die diese Regel (und ein halbes Dutzend anderer Eckfälle von C ++) nutzen, um Coroutinen mithilfe von C ++ 03-Regeln zu implementieren. Sie wurden erst in C ++ 20 in die Sprache aufgenommen, aber mit diesen Makros funktionierten sie ... solange Sie zuerst Ihre Antazida genommen haben!
Cort Ammon
58

Ja, das soll funktionieren. Die Fallbezeichnungen für eine switch-Anweisung in C entsprechen fast genau den goto-Bezeichnungen (mit einigen Einschränkungen hinsichtlich der Funktionsweise verschachtelter switch-Anweisungen). Insbesondere definieren sie selbst keine Blöcke für die Anweisungen, die Sie als "innerhalb des Falls" betrachten, und Sie können sie verwenden, um in die Mitte eines Blocks zu springen, wie Sie es mit einem goto könnten. Beim Springen in die Mitte eines Blocks gelten die gleichen Einschränkungen wie bei goto hinsichtlich des Springens über die Initialisierung von Variablen usw.

In der Praxis ist es wahrscheinlich klarer, dies mit einer goto-Anweisung zu schreiben, wie in:

    switch (i) {
    case 0:
        putchar('a');
        goto case2;
    case 1:
        putchar('b');
        // @fallthrough@
    case2:
    case 2:
        putchar('c');
        break;
    }
R .. GitHub HÖREN SIE AUF, EIS ZU HELFEN
quelle
1
"Wenn Sie in die Mitte eines Blocks springen, gelten die gleichen Einschränkungen wie bei goto für das Springen über die Initialisierung von Variablen usw." Welche Einschränkungen wären das? Sie können nicht über die Initialisierung von Variablen springen.
Asteroiden mit Flügeln
4
@AsteroidsWithWings, meinst du "kann nicht" aus einer standardkonformen Perspektive oder aus einer praktischen Perspektive, die ein Compiler nicht zulässt? Weil mein GCC es im C-Modus mit einer Warnung zulässt. Im C ++ - Modus ist dies jedoch nicht zulässig.
ilkkachu
5
@quetzalcoatl gcc-9 und clang-6 erlauben beide diesen Code und warnen vor möglicherweise nicht initialisiertem Code bee(im C-Modus; in C ++ wird der Fehler "Kann nicht von der switch-Anweisung zu diesem Fall springen / Sprung umgeht die Variableninitialisierung").
Ruslan
7
Sie wissen, dass Sie etwas falsch machen, wenn Sie gotodie sauberere Lösung verwenden.
Konrad Rudolph
9
@KonradRudolph: gotoist sehr bösartig, aber es ist wirklich nichts zu beanstanden, wenn Sie nicht manuell komplexe Kontrollstrukturen daraus machen. Bei dem ganzen "Goto als schädlich angesehen" ging es darum, HLL-Code (z. B. C) zu schreiben, der aussieht, als würde er von einem Compiler ausgegeben (jmps überall hin und her). Es gibt viele gut strukturierte Verwendungen von goto wie "Nur vorwärts" (konzeptionell nicht anders als breakoder früh return), "Nur Wiederholungsschleife" (vermeidet die Verschleierung des gemeinsamen Flusspfads und kompensiert das Fehlen verschachtelter continue) usw.
R .. GitHub STOP HELPING ICE
29

Wie andere Antworten bereits erwähnt haben, ist dies nach dem Standard technisch zulässig, für zukünftige Leser des Codes jedoch sehr verwirrend und unklar.

Aus diesem Grund switch ... casesollten Anweisungen normalerweise mit Funktionsaufrufen und nicht mit viel Inline-Code geschrieben werden.

switch(i) {
case 0:
    do_zero_case(); do_general_stuff(); break;
case 1:
    do_one_case(); do_general_stuff(); break;
case 2:
    do_general_stuff(); break;
default:
    do_default_not_zero_not_one_not_general_stuff(); break;
}
Pete Becker
quelle
Beachten Sie, dass der Code in der Frage nicht do_general_stuffstandardmäßig ist, sondern nur für Fall 2 (und 0 und 1). Der Schalter wird jedoch nie iaußerhalb des Bereichs 0..2 ausgeführt.
Peter Cordes
@PeterCordes - Beachten Sie, dass der Code in der Antwort nicht do_general_stuffstandardmäßig ist, sondern nur für Fall 2 (und 0 und 1).
Pete Becker
Ein Vorschlag - vielleicht sollte der Standardfall entfernt oder umbenannt werden? Es ist ziemlich leicht, die verschiedenen Funktionsnamen zu übersehen.
I.Am.A.Guy
@ PeteBecker: Oh, IDK, wie ich das verpasst habe generalund defaultverschiedene Wörter waren. OTOH, es ist eine bekannte Tatsache, dass Menschen normalerweise immer noch ein Wort lesen können, wenn die mittleren Buchstaben verschlüsselt sind. Wir neigen dazu, den Anfang und das Ende zu betrachten, daher ist es wahrscheinlich nicht ideal für die Skimmbarkeit, wenn nur die Mitte unterschiedlich ist. Entfernen Sie möglicherweise das do_Präfix.
Peter Cordes
1
@supercat: Bei dieser "bekannten Tatsache" geht es nicht darum, die mittleren Buchstaben durch andere zu ersetzen, sondern nur darum, die Reihenfolge von damals zu verwechseln. "Wenn der Cialm in Graneel wahr ist, bist du immer bereit, Tihs zu raiden."
Michael Karcher