Sintassi valida, ma senza valore in caso di commutazione?


207

Attraverso un piccolo errore di battitura, ho trovato per caso questo costrutto:

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

Sembra che la printfparte superiore switchdell'istruzione sia valida, ma anche completamente irraggiungibile.

Ho ottenuto una compilazione pulita, senza nemmeno un avvertimento sul codice irraggiungibile, ma questo sembra inutile.

Un compilatore dovrebbe contrassegnarlo come codice irraggiungibile?
Questo serve a qualche scopo?


51
GCC ha una bandiera speciale per questo. È-Wswitch-unreachable
Eli Sadoff il

57
"Questo serve a qualche scopo?" Bene, puoi entrare gotoe uscire dalla parte altrimenti irraggiungibile, che può essere utile per vari hack.
HolyBlackCat,

12
@HolyBlackCat Non sarebbe tale per tutto il codice non raggiungibile?
Eli Sadoff,

28
@EliSadoff Effettivamente. Immagino che non abbia alcuno scopo speciale . Scommetto che è permesso solo perché non c'è motivo di proibirlo. Dopotutto, switchè solo un condizionale gotocon più etichette. Ci sono più o meno le stesse restrizioni sul suo corpo come su un normale blocco di codice pieno di etichette goto.
HolyBlackCat,

16
Vale la pena sottolineare che l'esempio di @MooingDuck è una variante del dispositivo di Duff ( en.wikipedia.org/wiki/Duff's_device )
Michael Anderson,

Risposte:


226

Forse non il più utile, ma non del tutto inutile. È possibile utilizzarlo per dichiarare una variabile locale disponibile nell'ambito switch.

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

Lo standard ( N1579 6.8.4.2/7) ha il seguente esempio:

ESEMPIO Nel frammento del programma artificiale

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

l'oggetto il cui identificatore iesiste con durata di memorizzazione automatica (all'interno del blocco) ma non viene mai inizializzato, e quindi se l'espressione di controllo ha un valore diverso da zero, la chiamata alla printffunzione accederà a un valore indeterminato. Allo stesso modo, fnon è possibile raggiungere la chiamata alla funzione .

PS BTW, l'esempio non è un codice C ++ valido. In quel caso ( N4140 6.7/3, enfatizzare il mio):

Un programma che salta 90 da un punto in cui una variabile con durata di memorizzazione automatica non rientra nell'ambito in un punto in cui è nell'ambito è mal formato a meno che la variabile abbia un tipo scalare , un tipo di classe con un costruttore predefinito banale e un distruttore banale, una versione qualificata per cv di uno di questi tipi o una matrice di uno dei tipi precedenti ed è dichiarata senza un inizializzatore (8.5).


90) Il passaggio dalla condizione di una switchdichiarazione a un'etichetta del caso è considerato un salto in questo senso.

Quindi la sostituzione int i = 4;con lo int i;rende un C ++ valido.


3
"... ma non è mai inizializzato ..." Sembra che isia inizializzato su 4, cosa mi sto perdendo?
yano,

7
Nota che se la variabile è static, verrà inizializzata a zero, quindi anche per questo c'è un utilizzo sicuro.
Leushenko,

23
@yano Saltiamo sempre sull'inizializzazione i = 4;, quindi non ha mai luogo.
AlexD,

12
Ah certo! ... tutto il punto della domanda ... cavolo. Il desiderio è forte di eliminare questa stupidità
yano,

1
Bello! A volte avevo bisogno di una variabile temp all'interno di a case, e dovevo sempre usare nomi diversi in ciascuna caseo definirla al di fuori dell'interruttore.
SJuan76,

98

Questo serve a qualche scopo?

Sì. Se invece di una dichiarazione, metti una dichiarazione prima della prima etichetta, questo può avere perfettamente senso:

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

Le regole per le dichiarazioni e le istruzioni sono condivise per i blocchi in generale, quindi è la stessa regola che consente ciò che consente anche le istruzioni lì.


Vale anche la pena ricordare che se la prima istruzione è un costrutto loop, le etichette dei casi potrebbero apparire nel corpo del loop:

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

Si prega di non scrivere codice in questo modo se esiste un modo più leggibile per scriverlo, ma è perfettamente valido e la f()chiamata è raggiungibile.


Il dispositivo di @MatthieuM Duff ha etichette del caso all'interno di un ciclo, ma inizia con un'etichetta del caso prima del ciclo.

2
Non sono sicuro se dovrei votare per l'esempio interessante o downvote per la follia assoluta di scrivere questo in un vero programma :). Complimenti per esserti tuffato nell'abisso e tornare indietro in un unico pezzo.
Liviu T.

@ChemicalEngineer: se il codice fa parte di un ciclo, come nel dispositivo di Duff, { /*code*/ switch(x) { } }può sembrare più pulito ma è anche sbagliato .
Ben Voigt,

39

C'è un uso famoso di questo chiamato Duff's Device .

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

Qui copiamo un buffer puntato da fromun buffer puntato da to. Copiamo countistanze di dati.

L' do{}while()istruzione inizia prima della prima caseetichetta e le caseetichette sono incorporate all'interno di do{}while().

Ciò riduce il numero di rami condizionali alla fine del do{}while()loop incontrati all'incirca da un fattore 4 (in questo esempio; la costante può essere regolata su qualsiasi valore desiderato).

Ora, gli ottimizzatori a volte possono fare questo per te (specialmente se stanno ottimizzando le istruzioni di streaming / vettorializzate), ma senza l'ottimizzazione guidata dal profilo non possono sapere se ti aspetti che il ciclo sia grande o meno.

In generale, in questi casi possono verificarsi dichiarazioni di variabili che possono essere utilizzate in ogni caso, ma al di fuori dell'interruttore non rientrano nell'ambito di applicazione. (nota che qualsiasi inizializzazione verrà saltata)

Inoltre, il flusso di controllo che non è specifico dell'interruttore può portarti in quella sezione del blocco interruttore, come illustrato sopra, o con un goto.


2
Ovviamente, ciò sarebbe ancora possibile senza consentire dichiarazioni sopra il primo caso, poiché l'ordine di do {e case 0:non importa, entrambi servono a posizionare un obiettivo di salto sul primo *to = *from++;.
Ben Voigt,

1
@BenVoigt Direi che mettere il file do {è più leggibile. Sì, discutere sulla leggibilità del dispositivo di Duff è stupido e inutile e probabilmente un modo semplice per impazzire.
Finanzi la causa di Monica il

1
@QPaysTaxes Si dovrebbe verificare di Simon Tatham coroutine in C . O forse no.
Jonas Schäfer,

@ JonasSchäfer In modo divertente, questo è fondamentalmente ciò che le coroutine C ++ 20 faranno per te.
Yakk - Adam Nevraumont,

15

Supponendo che tu stia usando gcc su Linux, ti darebbe un avviso se stai usando la versione 4.4 o precedente.

L'opzione -Wunreachable-code è stata rimossa da gcc 4.4 in poi.


Avere riscontrato in prima persona il problema aiuta sempre!
16ton

@JonathanLeffler: Il problema generale che gli avvisi di gcc siano sensibili al particolare set di passaggi di ottimizzazione selezionati è purtroppo vero, e rende l'esperienza utente scadente. È davvero fastidioso avere una build di debug pulita seguita da una build di rilascio non riuscita: /
Matthieu M.

@MatthieuM .: Sembrerebbe che un simile avvertimento sarebbe estremamente facile da rilevare nei casi che implicano analisi grammaticali piuttosto che semantiche [ad esempio il codice segue un "if" che ha ritorni in entrambi i rami], e rendere più facile soffocare il "fastidio" avvertenze. D'altra parte, ci sono alcuni casi in cui è utile avere errori o avvisi nelle build di rilascio che non sono presenti nelle build di debug (se non altro, nei luoghi in cui si suppone che un hack che viene inserito per il debug debba essere ripulito per pubblicazione).
supercat

1
@MatthieuM .: Se fosse necessaria un'analisi semantica significativa per scoprire che una certa variabile sarà sempre falsa in qualche punto del codice, il codice che è condizionato dal fatto che quella variabile è vera sarebbe risultato irraggiungibile solo se tale analisi fosse eseguita. D'altra parte, prenderei in considerazione un avviso che tale codice era irraggiungibile in modo piuttosto diverso da un avvertimento sul codice sintatticamente irraggiungibile, poiché dovrebbe essere del tutto normale avere diverse condizioni possibili con alcune configurazioni di progetto ma non con altre. A volte può essere utile per i programmatori ...
supercat

1
... per sapere perché alcune configurazioni generano un codice più grande di altre [es. perché un compilatore potrebbe considerare alcune condizioni impossibili in una configurazione ma non in un'altra] ma ciò non significa che ci sia qualcosa di "sbagliato" nel codice che potrebbe essere ottimizzato in quella moda con alcune configurazioni.
supercat

11

Non solo per la dichiarazione variabile ma anche per il salto avanzato. Puoi utilizzarlo bene se e solo se non sei incline al codice spaghetti.

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

stampe

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

Va notato che il caso di commutazione è una delle clausole di flusso di controllo più veloci. Quindi deve essere molto flessibile per il programmatore, che a volte comporta casi come questo.


E qual è la differenza tra nocase:e default:?
i486,

@ i486 Quando i=4non si attiva nocase.
Sanchke Dellowar,

@SanchkeDellowar questo è ciò che intendo.
njzk2,

Perché diamine si dovrebbe fare questo invece di mettere semplicemente il caso 1 prima del caso 0 e usare il fallthrough?
Jonas Schäfer,

@JonasWielicki In questo obiettivo, potresti farlo. Ma questo codice è solo una vetrina di ciò che può essere fatto.
Sanchke Dellowar,

11

Va notato che non ci sono praticamente restrizioni strutturali sul codice all'interno switchdell'istruzione o su dove le case *:etichette sono inserite all'interno di questo codice *. Ciò rende possibili trucchi di programmazione come il dispositivo di Duff , una possibile implementazione della quale appare così:

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

Vedi, il codice tra la switch(n%8) {e l' case 7:etichetta è sicuramente raggiungibile ...


* Come ha sottolineato fortunatamente il supercat in un commento : dal C99, né una gotoné un'etichetta (che sia case *:un'etichetta o meno) può apparire nell'ambito di una dichiarazione che contiene una dichiarazione VLA. Quindi non è corretto dire che non ci sono nessun limitazioni strutturali sul posizionamento delle case *:etichette. Tuttavia, il dispositivo di Duff è precedente allo standard C99 e non dipende comunque dai VLA. Tuttavia, mi sono sentito obbligato a inserire un "virtualmente" nella mia prima frase a causa di ciò.


L'aggiunta di matrici di lunghezza variabile ha portato all'imposizione di restrizioni strutturali ad esse correlate.
supercat

@supercat Che tipo di restrizioni?
cmaster - ripristina monica il

1
Né etichetta gotoswitch/case/defaultetichetta possono apparire nell'ambito di alcun oggetto o tipo dichiarati in modo variabile. Ciò significa effettivamente che se un blocco contiene dichiarazioni di oggetti o tipi di array a lunghezza variabile, qualsiasi etichetta deve precedere tali dichiarazioni. C'è un po 'di confusione nella norma che suggerisce che in alcuni casi l'ambito di una dichiarazione VLA si estende per intero a un'istruzione switch; vedi stackoverflow.com/questions/41752072/… per la mia domanda al riguardo.
supercat

@supercat: hai appena frainteso quella verbosità (che presumo sia il motivo per cui hai eliminato la tua domanda). Implica un requisito nell'ambito in cui è possibile definire un VLA. Non estende tale ambito, rende solo non valide alcune definizioni VLA.
Keith Thompson,

@KeithThompson: Sì, l'avevo frainteso. Lo strano uso del presente nella nota a piè di pagina ha reso le cose confuse, e penso che il concetto avrebbe potuto essere meglio espresso come un divieto: "Un'istruzione switch il cui corpo contiene una dichiarazione VLA al suo interno non deve includere alcuna etichetta switch o case all'interno del campo di applicazione di tale dichiarazione VLA ".
supercat

10

Hai ottenuto la tua risposta correlata all'opzione richiestagcc -Wswitch-unreachable per generare l'avviso, questa risposta è quella di elaborare la parte di usabilità / dignità .

Citando direttamente C11, capitolo §6.8.4.2, ( sottolineatura mia )

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

l'oggetto il cui identificatore iesiste con durata di memorizzazione automatica (all'interno del blocco) ma non viene mai inizializzato e quindi se l'espressione di controllo ha un valore diverso da zero, la chiamata alla printf funzione accederà a un valore indeterminato. Allo stesso modo, fnon è possibile raggiungere la chiamata alla funzione .

Il che è molto esplicativo. È possibile utilizzarlo per definire una variabile con ambito locale disponibile solo nell'ambito switchdell'istruzione.


9

È possibile implementare un "loop e mezzo" con esso, anche se potrebbe non essere il modo migliore per farlo:

char password[100];
switch(0) do
{
  printf("Invalid password, try again.\n");
default:
  read_password(password, sizeof(password));
} while (!is_valid_password(password));

@RichardII È un gioco di parole o cosa? Spiega per favore.
Dancia,

1
@Dancia Sta dicendo che questo è abbastanza chiaramente non è il modo migliore per fare qualcosa di simile, e "potrebbe non" è qualcosa di riduttivo.
Finanzi la causa di Monica il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.