web-dev-qa-db-fra.com

Une syntaxe valide, mais sans valeur en cas de coupure?

À travers une petite faute de frappe, j'ai accidentellement trouvé cette construction:

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

Il semble que la printf située en haut de l'instruction switch soit valide, mais également totalement inaccessible.

J'ai une compilation propre, sans même un avertissement sur le code inaccessible, mais cela semble inutile.

Un compilateur doit-il signaler ceci comme un code inaccessible?
Cela sert-il à quelque chose?

206
abelenky

Peut-être pas le plus utile, mais pas complètement sans valeur. Vous pouvez l'utiliser pour déclarer une variable locale disponible dans switch scope.

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

La norme (N1579 6.8.4.2/7) a l'exemple suivant:

EXEMPLE Dans le fragment de programme artificiel

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

l'objet dont l'identificateur est i existe avec une durée de stockage automatique (dans le bloc) mais n'est jamais initialisé; ainsi, si l'expression de contrôle a une valeur non nulle, l'appel de la fonction printf accédera à une valeur indéterminée. valeur. De même, l'appel de la fonction f ne peut pas être atteint.

P.S. BTW, l'exemple n'est pas un code C++ valide. Dans ce cas (N4140 6.7/3, c'est moi qui souligne):

Un programme qui saute90 depuis un point où une variable avec une durée de stockage automatique n'est pas dans la portée jusqu'à un point où elle est dans la portée est mal formée sauf si la variable a un type scalaire , type de classe avec un constructeur par défaut trivial et un destructeur trivial, une version qualifiée de cv de l’un de ces types ou un tableau de l’un des types précédents et est déclaré sans initialiseur (8.5).


90) Le transfert de la condition d'une instruction switch à une étiquette de cas est considéré comme un saut à cet égard.

Donc, remplacer int i = 4; par int i; en fait un C++ valide.

225
AlexD

Est-ce que cela sert à quelque chose?

Oui. Si, au lieu d’une déclaration, vous placez une déclaration avant la première étiquette, cela peut avoir un sens parfait:

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

Les règles pour les déclarations et les instructions sont partagées pour les blocs en général, c'est donc la même règle qui permet cela, ainsi que les instructions.


Il convient également de mentionner que si la première instruction est une construction de boucle, des libellés de casse peuvent apparaître dans le corps de la boucle:

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

S'il vous plaît, n'écrivez pas un code comme celui-ci s'il existe un moyen plus lisible de l'écrire, mais il est parfaitement valide et l'appel f() est accessible.

98
user743382

Il y a une utilisation célèbre de ce appelé Device de Duff .

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

Ici, nous copions un tampon désigné par from dans un tampon désigné par to. Nous copions count instances de données.

L'instruction do{}while() commence avant la première étiquette case, et les étiquettes case sont incorporées à la do{}while().

Cela réduit le nombre de branches conditionnelles à la fin de la boucle do{}while() rencontrée d'environ 4 fois (dans cet exemple, la constante peut être ajustée à la valeur de votre choix).

Maintenant, les optimiseurs peuvent parfois le faire pour vous (en particulier s’ils optimisent les instructions de transmission en continu/vectorisées), mais sans optimisation guidée par le profil, ils ne peuvent pas savoir si vous vous attendez à ce que la boucle soit volumineuse ou non.

En général, les déclarations de variable peuvent s'y produire et être utilisées dans tous les cas, mais doivent être hors de portée une fois le commutateur terminé. (notez que toute initialisation sera ignorée)

De plus, un flux de contrôle qui n'est pas spécifique à un commutateur peut vous amener dans cette section du bloc de commutateur, comme illustré ci-dessus, ou avec un goto.

39

En supposant que vous utilisiez gcc sous Linux, cela vous aurait donné un avertissement si vous utilisiez la version 4.4 ou une version antérieure.

L'option -Wunreachable-code a été supprimée dans gcc 4.4 .

15
16tons

Il convient de noter qu’il n’existe pratiquement aucune restriction structurelle sur le code dans l’instruction switch ou sur l’emplacement où les étiquettes case *: sont placées dans ce code *. Cela rend des astuces de programmation telles que périphérique de duff , dont une implémentation possible ressemble à ceci:

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

Vous voyez, le code entre les étiquettes switch(n%8) { et case 7: est définitivement accessible ...


Comme supercat a heureusement souligné dans un commentaire : Depuis C99, ni une goto, ni une étiquette (que ce soit une étiquette case *: ou non) ne peuvent apparaître dans le cadre d'une déclaration qui contient une déclaration VLA. Il n'est donc pas correct de dire qu'il n'y a aucune restriction structurelle sur le placement des étiquettes case *:. Cependant, le périphérique de duff est antérieur à la norme C99 et il ne dépend en aucun cas de celui de VLA. Néanmoins, je me suis senti obligé d'insérer un "pratiquement" dans ma première phrase pour cette raison.

11
cmaster

Non seulement pour la déclaration de variable, mais aussi pour le saut avancé. Vous pouvez bien l'utiliser si et seulement si vous n'êtes pas enclin au code 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;
}

Impressions

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

Il convient de noter que le commutateur est l’une des clauses de flux de contrôle les plus rapides. Il doit donc être très flexible pour le programmeur, ce qui implique parfois des cas comme celui-ci.

11
Sanchke Dellowar

Vous avez obtenu votre réponse relative à l'option obligatoire gcc-Wswitch-unreachable pour générer l'avertissement; cette réponse consiste à élaborer sur l'utilisabilité / valeur digne partie.

Citant tout droit sorti de C11, chapitre §6.8.4.2, ( l'emphase mienne )

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

l'objet dont l'identificateur est i existe avec une durée de stockage automatique (dans le bloc) mais n'est jamais initialisé , et donc si l'expression de contrôle a valeur non nulle, l'appel de la fonction printf accédera à une valeur indéterminée. De même, l'appel de la fonction f ne peut pas être atteint.

Ce qui est très explicite. Vous pouvez l'utiliser pour définir une variable localement limitée qui est disponible uniquement dans la portée de l'instruction switch.

10
Sourav Ghosh

Il est possible de mettre en œuvre une "boucle et demie" avec elle, bien que ce ne soit peut-être pas la meilleure façon de le faire:

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