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.
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
16 Tonnen
Angenommen, Sie verwenden gcc unter Linux, hätten Sie eine Warnung erhalten, wenn Sie 4.4 oder eine frühere Version verwenden.
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
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
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.
14258900cookie-checkGültige, aber wertlose Syntax im Switch-Case?yes
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 Bedingunggoto
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