Gültige, aber wertlose Syntax im Switch-Case?

Lesezeit: 9 Minuten

Durch einen kleinen Tippfehler habe ich versehentlich dieses Konstrukt gefunden:

int main(void) {
    char foo = 'c';

    switch(foo)
    {
        printf("Cant Touch This\n");   // This line is Unreachable

        case 'a': printf("A\n"); break;
        case 'b': printf("B\n"); break;
        case 'c': printf("C\n"); break;
        case 'd': printf("D\n"); break;
    }

    return 0;
}

Es scheint, dass die printf an der Spitze der switch Aussage ist gültig, aber auch völlig unerreichbar.

Ich habe eine saubere Kompilierung erhalten, ohne auch nur eine Warnung über nicht erreichbaren Code, aber das scheint sinnlos zu sein.

Sollte ein Compiler dies als unerreichbaren Code kennzeichnen?
Erfüllt das überhaupt irgendeinen Zweck?

  • GCC hat dafür ein spezielles Flag. Es ist -Wswitch-unreachable

    – Eli Sadoff

    18. Januar 2017 um 19:08 Uhr

  • “Hat das überhaupt einen Zweck?” Ja, du kannst goto in und aus dem ansonsten unerreichbaren Teil, was für verschiedene Hacks nützlich sein kann.

    – HolyBlackCat

    18. Januar 2017 um 19:09 Uhr

  • @HolyBlackCat Wäre das nicht so für jeden unerreichbaren Code?

    – Eli Sadoff

    18. Januar 2017 um 19:10 Uhr

  • @EliSadoff In der Tat. Ich denke, es dient keinem Besondere Zweck. Ich wette, es ist erlaubt, nur weil es keinen Grund gibt, es zu verbieten. Schließlich, switch ist nur eine Bedingung goto mit mehreren Etiketten. Es gibt mehr oder weniger die gleichen Einschränkungen für seinen Körper wie für einen normalen Codeblock, der mit Goto-Labels gefüllt ist.

    – HolyBlackCat

    18. Januar 2017 um 19:14 Uhr


  • Es sei darauf hingewiesen, dass das Beispiel von @MooingDuck eine Variante auf Duffs Gerät ist (en.wikipedia.org/wiki/Duff’s_device)

    – Michael Anderson

    19. Januar 2017 um 2:14 Uhr

Benutzeravatar von AlexD
AlexD

Vielleicht nicht das Nützlichste, aber nicht vollständig wertlos. Sie können es verwenden, um eine verfügbare lokale Variable zu deklarieren switch Umfang.

switch (foo)
{
    int i;
case 0:
    i = 0;
    //....
case 1:
    i = 1;
    //....
}

Der Standard (N1579 6.8.4.2/7) hat das folgende Beispiel:

BEISPIEL Im künstlichen Programmfragment

switch (expr)
{
    int i = 4;
    f(i);
case 0:
    i = 17;
    /* falls through into default code */
default:
    printf("%d\n", i);
}

das Objekt, dessen Bezeichner ist i existiert mit automatischer Speicherdauer (innerhalb des Blocks), wird aber nie initialisiert, und daher wird, wenn der steuernde Ausdruck einen Wert ungleich Null hat, der Aufruf der printf Die Funktion greift auf einen unbestimmten Wert zu. Ebenso der Aufruf der Funktion f kann nicht erreicht werden.

PS Übrigens ist das Beispiel kein gültiger C++-Code. In diesem Fall (N4140 6.7/3Hervorhebung von mir):

Ein Programm, das springt90 von einem Punkt, an dem eine Variable mit automatischer Speicherdauer nicht im Geltungsbereich liegt, bis zu einem Punkt, an dem sie im Geltungsbereich liegt, ist falsch formatiert es sei denn, die Variable hat einen skalaren TypKlassentyp mit einem trivialen Standardkonstruktor und einem trivialen Destruktor, eine cv-qualifizierte Version eines dieser Typen oder ein Array eines der vorhergehenden Typen und wird ohne Initialisierer deklariert (8.5).


90) Die Übertragung von der Bedingung a switch Aussage zu einem Case-Label gilt in dieser Hinsicht als Sprung.

Also ersetzen int i = 4; mit int i; macht es zu einem gültigen C++.

  • “… wird aber nie initialisiert …” Sieht so aus i ist auf 4 initialisiert, was übersehe ich?

    – jano

    18. Januar 2017 um 19:24 Uhr

  • Beachten Sie, dass wenn die Variable ist staticwird es auf Null initialisiert, also gibt es auch dafür eine sichere Verwendung.

    – Leuschenko

    18. Januar 2017 um 19:29 Uhr

  • @yano Wir springen immer über die i = 4;Initialisierung, so dass es nie stattfindet.

    – Alex D

    18. Januar 2017 um 19:32 Uhr

  • Hach natürlich! … Kernpunkt der Frage … meine Güte. Der Wunsch ist groß, diese Dummheit zu löschen

    – jano

    18. Januar 2017 um 19:35 Uhr


  • Nett! Manchmal brauchte ich eine temporäre Variable in a caseund mussten in jedem immer unterschiedliche Namen verwenden case oder definieren Sie es außerhalb des Schalters.

    – SJuan76

    19. Januar 2017 um 13:08 Uhr

Erfüllt das überhaupt irgendeinen Zweck?

Ja. Wenn Sie statt einer Aussage eine Erklärung vor das erste Etikett stellen, kann dies durchaus Sinn machen:

switch (a) {
  int i;
case 0:
  i = f(); g(); h(i);
  break;
case 1:
  i = g(); f(); h(i);
  break;
}

Die Regeln für Deklarationen und Anweisungen gelten für Blöcke im Allgemeinen, also ist es die gleiche Regel, die erlaubt, dass dort auch Anweisungen erlaubt sind.


Erwähnenswert ist auch, dass, wenn die erste Anweisung ein Schleifenkonstrukt ist, Case-Labels im Schleifenkörper erscheinen können:

switch (i) {
  for (;;) {
    f();
  case 1:
    g();
  case 2:
    if (h()) break;
  }
}

Bitte schreiben Sie keinen Code wie diesen, wenn es eine lesbarere Art gibt, ihn zu schreiben, aber er ist vollkommen gültig, und die f() Anruf erreichbar ist.

  • Das Gerät von @MatthieuM Duff hat Fallbeschriftungen in einer Schleife, beginnt jedoch mit einer Fallbeschriftung vor der Schleife.

    Benutzer743382

    19. Januar 2017 um 15:38 Uhr

  • Ich bin mir nicht sicher, ob ich für das interessante Beispiel stimmen oder für den völligen Wahnsinn, dies in einem echten Programm zu schreiben, ablehnen sollte :). Herzlichen Glückwunsch, dass Sie in den Abgrund getaucht sind und in einem Stück zurückgekehrt sind.

    – Liviu T.

    19. Januar 2017 um 23:44 Uhr

  • @ChemicalEngineer: Wenn der Code Teil einer Schleife ist, wie es in Duffs Gerät der Fall ist, { /*code*/ switch(x) { } } sieht sauberer aus, ist es aber auch falsch.

    – Ben Voigt

    20. Januar 2017 um 16:56 Uhr


Es gibt eine berühmte Verwendung dieses Namens Duffs Gerät.

int n = (count+3)/4;
switch (count % 4) {
  do {
    case 0: *to = *from++;
    case 3: *to = *from++;
    case 2: *to = *from++;
    case 1: *to = *from++;
  } while (--n > 0);
}

Hier kopieren wir einen Puffer, auf den gezeigt wird from zu einem Puffer, auf den von gezeigt wird to. Wir kopieren count Instanzen von Daten.

Das do{}while() Anweisung beginnt vor der ersten case Etikett und die case Labels sind eingebettet in die do{}while().

Dadurch wird die Anzahl der bedingten Verzweigungen am Ende von reduziert do{}while() Schleife, die ungefähr um den Faktor 4 angetroffen wird (in diesem Beispiel kann die Konstante auf jeden gewünschten Wert angepasst werden).

Jetzt können Optimierer dies manchmal für Sie tun (insbesondere wenn sie Streaming-/vektorisierte Anweisungen optimieren), aber ohne profilgeführte Optimierung können sie nicht wissen, ob Sie erwarten, dass die Schleife groß ist oder nicht.

Im Allgemeinen können dort Variablendeklarationen vorkommen und in jedem Fall verwendet werden, aber nach dem Ende des Schalters außerhalb des Gültigkeitsbereichs liegen. (Beachten Sie, dass jede Initialisierung übersprungen wird)

Darüber hinaus kann Sie ein nicht Switch-spezifischer Kontrollfluss in diesen Abschnitt des Switch-Blocks führen, wie oben dargestellt, oder mit a goto.

  • Natürlich wäre dies noch möglich, ohne Aussagen über den ersten Fall hinaus zuzulassen, wie in der Größenordnung von do { und case 0: egal, beide dienen dazu, auf dem ersten ein Sprungziel zu platzieren *to = *from++;.

    – Ben Voigt

    20. Januar 2017 um 16:58 Uhr

  • @BenVoigt Ich würde argumentieren, dass das Setzen der do { ist besser lesbar. Ja, über die Lesbarkeit von Duffs Gerät zu streiten, ist dumm und sinnlos und wahrscheinlich eine einfache Möglichkeit, verrückt zu werden.

    – Nik

    20. Januar 2017 um 20:31 Uhr

  • @QPaysTaxes Sie sollten sich Simon Tathams ansehen Koroutinen in C. Oder vielleicht nicht.

    – Jonas Schäfer

    23. Januar 2017 um 6:33 Uhr


  • @JonasSchäfer Amüsanterweise ist das im Grunde das, was C++20-Koroutinen für Sie tun werden.

    – Yakk – Adam Nevraumont

    28. April 2019 um 15:46 Uhr

Benutzeravatar von 16tons
16 Tonnen

Angenommen, Sie verwenden gcc unter Linux, hätten Sie eine Warnung erhalten, wenn Sie 4.4 oder eine frühere Version verwenden.

Die Option -Wunreachable-code wurde in gcc 4.4 entfernt weiter.

Benutzeravatar von Dellowar
Dellowar

Nicht nur für die Variablendeklaration, sondern auch für fortgeschrittenes Springen. Sie können es nur dann gut nutzen, wenn Sie nicht zu Spaghetti-Code neigen.

int main()
{
    int i = 1;
    switch(i)
    {
        nocase:
        printf("no case\n");

        case 0: printf("0\n"); break;
        case 1: printf("1\n"); goto nocase;
    }
    return 0;
}

Drucke

1
no case
0 /* Notice how "0" prints even though i = 1 */

Es sollte beachtet werden, dass switch-case eine der schnellsten Kontrollflussklauseln ist. Daher muss es für den Programmierer sehr flexibel sein, was manchmal solche Fälle mit sich bringt.

  • Und was ist der Unterschied zwischen nocase: und default:?

    – i486

    20. Januar 2017 um 10:45 Uhr

  • @i486 Wanni=4 es löst nicht aus nocase.

    – Dellowar

    20. Januar 2017 um 14:14 Uhr

  • @SanchkeDellowar das meine ich.

    – njzk2

    20. Januar 2017 um 15:03 Uhr

  • Warum zum Teufel sollte man das tun, anstatt einfach Fall 1 vor Fall 0 zu setzen und Fallthrough zu verwenden?

    – Jonas Schäfer

    23. Januar 2017 um 6:37 Uhr

  • @JonasWielicki In diesem Ziel könntest du das tun. Aber dieser Code ist nur ein Schaukasten dessen, was getan werden kann.

    – Dellowar

    23. Januar 2017 um 15:29 Uhr

Benutzeravatar der Community
Gemeinschaft

Es sollte beachtet werden, dass es praktisch keine strukturellen Einschränkungen für den Code innerhalb der gibt switch Aussage, oder wo die case *: Etiketten werden innerhalb dieses Codes* platziert. Das macht Programmiertricks wie Duffs Gerät möglich, eine mögliche Implementierung sieht so aus:

int n = ...;
int iterations = n/8;
switch(n%8) {
    while(iterations--) {
        sum += *ptr++;
        case 7: sum += *ptr++;
        case 6: sum += *ptr++;
        case 5: sum += *ptr++;
        case 4: sum += *ptr++;
        case 3: sum += *ptr++;
        case 2: sum += *ptr++;
        case 1: sum += *ptr++;
        case 0: ;
    }
}

Sie sehen, der Code zwischen den switch(n%8) { und die case 7: Label ist auf jeden Fall erreichbar…


* Wie Supercat dankenswerterweise in einem Kommentar darauf hinwies: Seit C99 weder a goto noch ein Etikett (sei es ein case *: Etikett oder nicht) kann im Rahmen einer Deklaration erscheinen, die eine VLA-Deklaration enthält. Es ist also nicht richtig zu sagen, dass es sie gibt nein strukturelle Beschränkungen für die Platzierung der case *: Etiketten. Das Gerät von Duff ist jedoch älter als der C99-Standard und hängt sowieso nicht von VLAs ab. Trotzdem fühlte ich mich aufgrund dessen gezwungen, in meinen ersten Satz ein „virtually“ einzufügen.

  • Und was ist der Unterschied zwischen nocase: und default:?

    – i486

    20. Januar 2017 um 10:45 Uhr

  • @i486 Wanni=4 es löst nicht aus nocase.

    – Dellowar

    20. Januar 2017 um 14:14 Uhr

  • @SanchkeDellowar das meine ich.

    – njzk2

    20. Januar 2017 um 15:03 Uhr

  • Warum zum Teufel sollte man das tun, anstatt einfach Fall 1 vor Fall 0 zu setzen und Fallthrough zu verwenden?

    – Jonas Schäfer

    23. Januar 2017 um 6:37 Uhr

  • @JonasWielicki In diesem Ziel könntest du das tun. Aber dieser Code ist nur ein Schaukasten dessen, was getan werden kann.

    – Dellowar

    23. Januar 2017 um 15:29 Uhr

Benutzeravatar von Sourav Ghosh
Sourav Ghosh

Sie haben Ihre Antwort im Zusammenhang mit der erforderlich gcc Möglichkeit -Wswitch-unreachable Um die Warnung zu generieren, ist diese Antwort auf die näher zu erläutern Benutzerfreundlichkeit / Würdigkeit Teil.

Zitieren direkt aus C11Kapitel §6.8.4.2, (Betonung von mir)

switch (expr)
{
int i = 4;
f(i);
case 0:
i = 17;
/* falls through into default code */
default:
printf("%d\n", i);
}

das Objekt, dessen Bezeichner ist i existiert mit automatischer Speicherdauer (innerhalb des Blocks), wird aber nie initialisiertund wenn der steuernde Ausdruck einen Wert ungleich Null hat, wird der Aufruf von the printf
Die Funktion greift auf einen unbestimmten Wert zu. Ebenso der Aufruf der Funktion f kann nicht erreicht werden.

Was sehr selbsterklärend ist. Sie können dies verwenden, um eine lokal gültige Variable zu definieren, die nur innerhalb von verfügbar ist switch Geltungsbereich der Aussage.

1425890cookie-checkGültige, aber wertlose Syntax im Switch-Case?

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

Privacy policy