Geldige, maar waardeloze syntaxis in switch-case?

Door een kleine typfout vond ik per ongeluk deze constructie:

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

Het lijkt erop dat de printfbovenaan de switch-instructie geldig is, maar ook volledig onbereikbaar.

Ik heb een schone compilatie gekregen, zonder zelfs maar een waarschuwing over onbereikbare code, maar dit lijkt zinloos.

Moet een compiler dit markeren als onbereikbare code?
Heeft dit enig doel?


Antwoord 1, autoriteit 100%

Misschien niet de meest bruikbare, maar niet volledigwaardeloos. U kunt het gebruiken om een ​​lokale variabele beschikbaar te declareren binnen het bereik van switch.

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

De standaard (N1579 6.8.4.2/7) heeft het volgende voorbeeld:

VOORBEELD    In het kunstmatige programmafragment

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

het object waarvan de identifier iis, bestaat met automatische opslagduur (binnen het blok) maar is nooit
geïnitialiseerd, en dus als de controlerende expressie een waarde heeft die niet nul is, zal de aanroep van de functie printf
toegang tot een onbepaalde waarde. Evenzo kan de aanroep van de functie fniet worden bereikt.

P.S.Trouwens, het voorbeeld is geen geldige C++-code. In dat geval (N4140 6.7/3, cursivering van mij):

Een programma dat90springt van een punt waar een variabele met automatische opslagduur niet binnen het bereik valt, naar een
punt waar het binnen het bereik valt, is slecht gevormd tenzij de variabele een scalair type heeft, klassetype met een triviale standaard
constructor en een triviale destructor, een cv-gekwalificeerde versie van een van deze typen, of een array van een van de
voorgaande typen en wordt gedeclareerd zonder initialisatie(8.5).


90) De overdracht van de voorwaarde van een switch-statement naar een case-label wordt in dit opzicht als een sprong beschouwd.

Dus door int i = 4;te vervangen door int i;wordt het een geldige C++.


Antwoord 2, autoriteit 43%

Heeft dit enig doel?

Ja. Als u in plaats van een verklaring een verklaring voor het eerste label plaatst, kan dit volkomen logisch zijn:

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

De regels voor declaraties en statements worden gedeeld voor blokken in het algemeen, dus het is dezelfde regel die toestaat dat ook daar statements worden toegestaan.


Het is ook het vermelden waard dat als de eerste instructie een lusconstructie is, er hoofdletterlabels in de lustekst kunnen verschijnen:

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

Schrijf alsjeblieft geen code als deze als er een meer leesbare manier is om het te schrijven, maar het is perfect geldig, en de f()-aanroep is bereikbaar.


Antwoord 3, autoriteit 18%

Er is een beroemd gebruik hiervan genaamd 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);
}

Hier kopiëren we een buffer waarnaar wordt verwezen door fromnaar een buffer waarnaar wordt verwezen door to. We kopiëren countinstanties van gegevens.

De do{}while()-instructie begint vóór het eerste case-label, en de case-labels zijn ingesloten in de do{}while().

Dit vermindert het aantal voorwaardelijke vertakkingen aan het einde van de do{}while()-lus die u tegenkomt met ongeveer een factor 4 (in dit voorbeeld; de constante kan worden aangepast aan de waarde die u wil).

Nu kunnen optimizers dit soms voor u doen (vooral als ze streaming/gevectoriseerde instructies optimaliseren), maar zonder profielgestuurde optimalisatie kunnen ze niet weten of u verwacht dat de lus groot is of niet.

Over het algemeen kunnen daar variabele declaraties voorkomen en in alle gevallen worden gebruikt, maar deze vallen buiten het bereik nadat de switch is beëindigd. (let op eventuele initialisatie wordt overgeslagen)

Bovendien kan een besturingsstroom die niet specifiek voor een schakelaar is, u in dat gedeelte van het schakelblok brengen, zoals hierboven geïllustreerd, of met een goto.


Antwoord 4, autoriteit 7%

Ervan uitgaande dat je gcc op Linux gebruikt, zou het je een waarschuwing hebben gegeven als je 4.4 of een eerdere versie gebruikt.

De optie -Wunreachable-code is verwijderd in gcc 4.4 verder.


Antwoord 5, autoriteit 5%

Niet alleen voor variabele declaratie, maar ook voor geavanceerd springen. Je kunt het goed gebruiken als en alleen als je niet gevoelig bent voor spaghetti-code.

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

Afdrukken

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

Opgemerkt moet worden dat schakelkast een van de snelste regelstroomclausules is. Het moet dus heel flexibel zijn voor de programmeur, wat soms met dit soort gevallen te maken heeft.


Antwoord 6, autoriteit 5%

Opgemerkt moet worden dat er vrijwel geen structurele beperkingen zijn aan de code binnen de switch-instructie, of waar de case *:-labels in deze code worden geplaatst *. Dit maakt programmeertrucs zoals duff’s devicemogelijk, waarvan een mogelijke implementatie er als volgt uitziet:

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

Zie je, de code tussen het switch(n%8) {en het case 7:label is zeker bereikbaar…


* Zoals supercat dankbaar opmerkte in een opmerking: Sinds C99 mag noch een gotonoch een label (of het nu een case *:label is of niet) verschijnen binnen het bereik van een verklaring die een VLA bevat verklaring. Het is dus niet correct om te zeggen dat er geenstructurele beperkingen zijn op de plaatsing van de case *:labels. Het apparaat van duff dateert echter van vóór de C99-standaard en is sowieso niet afhankelijk van VLA’s. Desondanks voelde ik me hierdoor genoodzaakt om een ​​”vrijwel” in mijn eerste zin in te voegen.


Antwoord 7, autoriteit 4%

Je antwoord is gerelateerd aan de verplichte gccoptie -Wswitch-unreachableom de waarschuwing te genereren, dit antwoord is om het gedeelte bruikbaarheid/ waardigheiduit te werken.

Citeer rechtstreeks uit C11, hoofdstuk §6.8.4.2, (nadruk van mij)

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

het object waarvan de identifier iis, bestaat met automatische opslag
duur (binnen het blok) maar wordt nooit geïnitialiseerd
, en dus als de
controlerende expressie heeft een waarde die niet nul is, de aanroep van de printf
functie krijgt toegang tot een onbepaalde waarde. Zo ook de oproep om
de functie fkan niet worden bereikt.

Wat heel duidelijk is. U kunt dit gebruiken om een ​​variabele met een lokaal bereik te definiëren die alleen beschikbaar is binnen het bereik van de switch-instructie.


Antwoord 8, autoriteit 4%

Het is mogelijk om er een “lus en een half” mee te implementeren, hoewel dit misschien niet de beste manier is om het te doen:

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

Other episodes