Pourquoi les variables ne peuvent-elles pas être déclarées dans une instruction switch?

945

Je me suis toujours demandé ceci - pourquoi ne pouvez-vous pas déclarer des variables après une étiquette de cas dans une instruction switch? En C ++, vous pouvez déclarer des variables à peu près n'importe où (et les déclarer proches de la première utilisation est évidemment une bonne chose) mais ce qui suit ne fonctionnera toujours pas:

switch (val)  
{  
case VAL:  
  // This won't work
  int newVal = 42;  
  break;
case ANOTHER_VAL:  
  ...
  break;
}  

Ce qui précède me donne l'erreur suivante (MSC):

l'initialisation de «newVal» est ignorée par le libellé «case»

Cela semble également être une limitation dans d'autres langues. Pourquoi est-ce un tel problème?

Rob
la source
10
Pour une explication basée sur la grammaire C BNF, voir stackoverflow.com/questions/1180550/weird-switch-error-in-obj-c/…
johne
Voici une très bonne lecture sur les instructions et les étiquettes de commutateur (ABC :) en général.
Etherealone
4
Je dirais `` Pourquoi les variables ne peuvent-elles pas être initialisées dans une instruction switch plutôt que déclarées '', puisque la simple déclaration de la variable ne me donne qu'un avertissement dans MSVC.
ZoomIn

Réponses:

1144

Caseles instructions ne sont que des étiquettes . Cela signifie que le compilateur interprétera cela comme un saut directement vers l'étiquette. En C ++, le problème ici est celui de la portée. Vos accolades définissent la portée comme tout à l'intérieur duswitch instruction. Cela signifie que vous vous retrouvez avec une portée où un saut sera effectué plus loin dans le code en sautant l'initialisation.

La bonne façon de gérer cela est de définir une portée spécifique à cette caseinstruction et de définir votre variable à l'intérieur:

switch (val)
{   
case VAL:  
{
  // This will work
  int newVal = 42;  
  break;
}
case ANOTHER_VAL:  
...
break;
}
TJ Seabrooks
la source
94
Par rapport à l'ouverture d'une nouvelle portée - privilégiez la lisibilité et la cohérence du code. Dans le passé, vous pouviez avoir automatiquement un cadre de pile "supplémentaire", mais maintenant cela ne devrait pas être le cas pour tout compilateur d'optimisation décent.
Tall Jeff
10
Je suis d'accord avec Jeff - il est trop facile de "supposer" la portée lors de la lecture d'une instruction switch en raison du style d'indentation que la plupart des gens utilisent. Mon propre style est de toujours ouvrir une nouvelle portée pour chaque cas / défaut si elle est longue de plus d'une ligne.
Enchères du
39
workmad3 - Pouvez-vous me trouver un compilateur C ++ qui générera un nouveau cadre de pile si vous ne déclarez aucune nouvelle variable? Vous m'avez inquiété brièvement, mais aucun de G ++ 3.1, Visual C ++ 7 ou Intel C ++ 8 ne générera de code pour de nouvelles étendues où vous ne déclarez aucune variable.
Chris Jefferson
10
@ workmad3 en entrant un nouveau bloc d'accolades ne provoque pas de nouveau cadre de pile stackoverflow.com/questions/2759371/…
MTVS
3
@TallJef Je ne sais pas de quel «vieux temps» vous parlez. Je n'ai jamais rencontré de compilateur où tout l'espace de pile d'une méthode n'est pas alloué lorsque la méthode est entrée, en 40 ans.
Marquis de Lorne
333

Cette question est a été étiqueté comme [C] et [C ++] en même temps. Le code d'origine est en effet invalide à la fois en C et C ++, mais pour des raisons totalement différentes et sans rapport.

  • En C ++, ce code n'est pas valide car l' case ANOTHER_VAL:étiquette saute dans la portée de la variable en newValcontournant son initialisation. Les sauts qui contournent l'initialisation des objets automatiques sont illégaux en C ++. Ce côté du problème est correctement traité par la plupart des réponses.

  • Cependant, en langage C, le contournement de l'initialisation des variables n'est pas une erreur. Sauter dans la portée d'une variable sur son initialisation est légal en C. Cela signifie simplement que la variable n'est pas initialisée. Le code d'origine ne compile pas en C pour une raison complètement différente. L'étiquette case VAL:dans le code d'origine est attachée à la déclaration de variable newVal. En langage C, les déclarations ne sont pas des déclarations. Ils ne peuvent pas être étiquetés. Et c'est ce qui provoque l'erreur lorsque ce code est interprété comme du code C.

    switch (val)  
    {  
    case VAL:             /* <- C error is here */
      int newVal = 42;  
      break;
    case ANOTHER_VAL:     /* <- C++ error is here */
      ...
      break;
    }

Ajout d'un extra {} bloc résout à la fois les problèmes C ++ et C, même si ces problèmes sont très différents. Du côté C ++, il restreint la portée de newVal, en s'assurant que case ANOTHER_VAL:ne saute plus dans cette portée, ce qui élimine le problème C ++. Du côté C, ce supplément {}introduit une instruction composée, ce qui rend l' case VAL:étiquette à appliquer à une instruction, ce qui élimine le problème C.

  • Dans le cas C, le problème peut être facilement résolu sans {} . Ajoutez simplement une instruction vide après l' case VAL:étiquette et le code deviendra valide

    switch (val)  
    {  
    case VAL:;            /* Now it works in C! */
      int newVal = 42;  
      break;
    case ANOTHER_VAL:  
      ...
      break;
    }

    Notez que même s'il est désormais valide du point de vue C, il reste invalide du point de vue C ++.

  • Symétriquement, dans le cas C ++, le problème peut être facilement résolu sans le {} . Il suffit de supprimer l'initialiseur de la déclaration de variable et le code deviendra valide

    switch (val)  
    {  
    case VAL: 
      int newVal;
      newVal = 42;  
      break;
    case ANOTHER_VAL:     /* Now it works in C++! */
      ...
      break;
    }

    Notez que même s'il est désormais valide du point de vue C ++, il reste invalide du point de vue C.

Fourmi
la source
4
@AnT: Je comprends pourquoi celui qui corrige C ++ n'est pas applicable pour C; cependant, je ne peux pas comprendre comment cela résout le problème C ++ de sauter l'initialisation en premier lieu? Ne sauterait-il toujours pas la déclaration et l'affectation du newValmoment où il saute ANOTHER_VAL?
legends2k
13
@ legends2k: Oui, il l'ignore toujours. Cependant, quand je dis "cela résout le problème", je veux dire qu'il corrige l'erreur du compilateur C ++ . En C ++, il est illégal de sauter une déclaration scalaire avec initialiseur , mais il est parfaitement correct de sauter une déclaration scalaire sans initialiseur . Au case ANOTHER_VAL:point variable newValest visible, mais avec une valeur indéterminée.
2015
3
Fascinant. J'ai trouvé cette question après avoir lu §A9.3: Compound Statementdans K&R C (deuxième édition). L'entrée mentionne la définition technique d'une déclaration composée qui est {declaration-list[opt] statement-list[opt]}. Confus, parce que j'avais pensé qu'une déclaration était une déclaration, je l'ai recherchée et j'ai immédiatement trouvé cette question, un exemple où cette disparité devient apparente et rompt en fait un programme. Je crois qu'une autre solution (pour C) serait de mettre une autre déclaration (éventuellement une déclaration nulle?) Avant la déclaration afin que la déclaration étiquetée soit satisfaite.
Braden Best
Oups, je viens de remarquer que la solution de déclaration nulle que j'ai suggérée est déjà dans votre réponse. Pas de soucis alors.
Braden Best
3
Il convient de noter que la correction de l'ajout d'une instruction vide ne fonctionne que pour C99. En C89, les variables doivent être déclarées au début de leur bloc englobant.
Arthur Tacca
136

D'accord. Juste pour clarifier cela, cela n'a strictement rien à voir avec la déclaration. Elle concerne uniquement le "saut par-dessus l'initialisation" (ISO C ++ '03 6.7 / 3)

Beaucoup de messages ici ont mentionné que le fait de sauter la déclaration peut entraîner la non-déclaration de la variable. Ce n'est pas vrai. Un objet POD peut être déclaré sans initialiseur mais il aura une valeur indéterminée. Par exemple:

switch (i)
{
   case 0:
     int j; // 'j' has indeterminate value
     j = 0; // 'j' initialized to 0, but this statement
            // is jumped when 'i == 1'
     break;
   case 1:
     ++j;   // 'j' is in scope here - but it has an indeterminate value
     break;
}

Lorsque l'objet est un non-POD ou un agrégat, le compilateur ajoute implicitement un initialiseur, et il n'est donc pas possible de passer par-dessus une telle déclaration:

class A {
public:
  A ();
};

switch (i)  // Error - jumping over initialization of 'A'
{
   case 0:
     A j;   // Compiler implicitly calls default constructor
     break;
   case 1:
     break;
}

Cette limitation n'est pas limitée à l'instruction switch. C'est aussi une erreur d'utiliser 'goto' pour sauter une initialisation:

goto LABEL;    // Error jumping over initialization
int j = 0; 
LABEL:
  ;

Un petit anecdote est que c'est une différence entre C ++ et C. En C, ce n'est pas une erreur de sauter par-dessus l'initialisation.

Comme d'autres l'ont mentionné, la solution consiste à ajouter un bloc imbriqué afin que la durée de vie de la variable soit limitée à l'étiquette de cas individuel.

Richard Corden
la source
2
"Erreur lors du saut d'initialisation" ??? Pas avec mon GCC. Il peut donner un avertissement «j peut être utilisé unialisé» lors de l'utilisation de j sous l'étiquette, mais il n'y a pas d'erreur. Cependant, en cas de changement, il y a une erreur (une erreur grave, pas un faible avertissement).
Mecki
9
@Mecki: C'est illégal en C ++. ISO C ++ '03 - 6.7 / 3: "... Un programme qui saute d'un point où une variable locale avec une durée de stockage automatique n'est pas dans la portée à un point où elle est dans la portée est mal formé à moins que la variable ait le type POD (3.9) et est déclaré sans initialiseur (8.5). "
Richard Corden
1
Oui, mais ce n'est pas illégal en C (au moins gcc dit que ce n'est pas le cas). j ne sera pas initialisé (aura un certain nombre aléatoire), mais le compilateur le compile. Cependant, dans le cas de l'instruction switch, le compilateur ne la compilera même pas et je ne vois pas la différence entre un cas goto / label et un cas de commutateur.
Mecki
8
@Mecki: En général, un comportement de compilateur unique ne reflète pas nécessairement ce qui est réellement autorisé par le langage. J'ai vérifié les deux C'90 et C'99 et les deux normes incluent un exemple avec un saut par-dessus l'initialisation dans une instruction switch.
Richard Corden
38

L'instruction switch entière est dans la même portée. Pour le contourner, procédez comme suit:

switch (val)
{
    case VAL:
    {
        // This **will** work
        int newVal = 42;
    }
    break;

    case ANOTHER_VAL:
      ...
    break;
}

Notez les crochets.

Mark Ingram
la source
30

Après avoir lu toutes les réponses et quelques recherches supplémentaires, je reçois quelques choses.

Case statements are only 'labels'

En C, selon la spécification,

§6.8.1 Déclarations étiquetées:

labeled-statement:
    identifier : statement
    case constant-expression : statement
    default : statement

En C, aucune clause ne permet une "déclaration étiquetée". Cela ne fait tout simplement pas partie de la langue.

Donc

case 1: int x=10;
        printf(" x is %d",x);
break;

Cela ne se compilera pas , voir http://codepad.org/YiyLQTYw . GCC donne une erreur:

label can only be a part of statement and declaration is not a statement

Même

  case 1: int x;
          x=10;
            printf(" x is %d",x);
    break;

ce n'est pas non plus la compilation , voir http://codepad.org/BXnRD3bu . Ici, je reçois également la même erreur.


En C ++, selon la spécification,

La déclaration étiquetée est autorisée mais l'initialisation étiquetée n'est pas autorisée.

Voir http://codepad.org/ZmQ0IyDG .


La solution à une telle condition est deux

  1. Soit utiliser une nouvelle portée en utilisant {}

    case 1:
           {
               int x=10;
               printf(" x is %d", x);
           }
    break;
  2. Ou utilisez une déclaration fictive avec une étiquette

    case 1: ;
               int x=10;
               printf(" x is %d",x);
    break;
  3. Déclarez la variable avant switch () et initialisez-la avec différentes valeurs dans l'instruction case si elle répond à vos besoins

    main()
    {
        int x;   // Declare before
        switch(a)
        {
        case 1: x=10;
            break;
    
        case 2: x=20;
            break;
        }
    }

Encore plus de choses avec l'instruction switch

N'écrivez jamais dans le commutateur des instructions qui ne font partie d'aucune étiquette, car elles ne seront jamais exécutées:

switch(a)
{
    printf("This will never print"); // This will never executed

    case 1:
        printf(" 1");
        break;

    default:
        break;
}

Voir http://codepad.org/PA1quYX3 .

Jeegar Patel
la source
2
Vous avez correctement décrit le problème C. Mais l'affirmation selon laquelle l'initialisation étiquetée en C ++ n'est pas autorisée n'est absolument pas vraie. Il n'y a rien de mal à l'initialisation étiquetée en C ++. Ce que C ++ ne permet pas, c'est de sauter l' initialisation de la variable adans la portée de la variable a. Donc, du point de vue C, le problème vient de l' case VAL:étiquette et vous l'avez décrite correctement. Mais du point de vue C ++, le problème est avec l' case ANOTHER_VAL:étiquette.
AnT
En C ++, contrairement à C, les déclarations sont un sous-ensemble d'instructions.
Keith Thompson
20

Vous ne pouvez pas faire cela, car les caseétiquettes ne sont en fait que des points d'entrée dans le bloc conteneur.

Ceci est plus clairement illustré par l'appareil de Duff . Voici un code de Wikipedia:

strcpy(char *to, char *from, size_t count) {
    int n = (count + 7) / 8;
    switch (count % 8) {
    case 0: do { *to = *from++;
    case 7:      *to = *from++;
    case 6:      *to = *from++;
    case 5:      *to = *from++;
    case 4:      *to = *from++;
    case 3:      *to = *from++;
    case 2:      *to = *from++;
    case 1:      *to = *from++;
               } while (--n > 0);
    }
}

Remarquez comment les caseétiquettes ignorent totalement les limites des blocs. Oui, c'est mal. Mais c'est pourquoi votre exemple de code ne fonctionne pas. Passer à une caseétiquette équivaut à utiliser goto, vous n'êtes donc pas autorisé à passer par-dessus une variable locale avec un constructeur.

Comme plusieurs autres affiches l'ont indiqué, vous devez mettre votre propre bloc:

switch (...) {
    case FOO: {
        MyObject x(...);
        ...
        break; 
    }
    ...
 }
emk
la source
1
L'implémentation de cet appareil Duff a un bug qui le rend extrêmement lent: count est de type int donc le% doit effectuer une véritable opération de division / modulo. Ne comptez pas (ou mieux encore, utilisez toujours size_t pour les comptes / indices) et le problème disparaît.
R .. GitHub STOP HELPING ICE
1
@R ..: Quoi?! Dans un système de complément à deux, la signature n'affecte pas les modules par puissances de 2 (c'est juste un ET sur les bits inférieurs), et n'affecte pas les divisions par puissances de 2 tant que votre architecture de processeur a une opération arithmétique de décalage à droite ( SARen x86, contre SHRlequel correspond aux décalages non signés).
Chris Jester-Young du
@Chris: Je pense qu'il veut dire quand le compilateur doit permettre des valeurs négatives où "juste un ET sur les bits du bas" ne tient pas; par exemple, -1% 8 donne -1 sur le système de complément à deux en utilisant g ++ (le signe dans ce cas est l'implémentation définie par 5.6 / 4).
3
@Chris: Je suis d'accord avec vous que R exagère l'impact; Je n'ai vu que votre commentaire et je savais qu'un simple ET ne suffisait pas.
1
Il convient également de noter que le code Wikipedia d'origine est destiné à envoyer des données à une sortie mappée en mémoire, ce qui semble étrange ici car il n'est pas mentionné et chaque octet est copié au même emplacement "vers". Pourrait contourner cela en ajoutant postfix ++ au to, ou en mentionnant le cas d'utilisation pour les E / S mappées en mémoire. Totalement périphérique à la question d'origine :-).
Peter
16

Jusqu'à présent, la plupart des réponses sont fausses sur un point: vous pouvez déclarer des variables après l'instruction case, mais vous ne pouvez pas les initialiser:

case 1:
    int x; // Works
    int y = 0; // Error, initialization is skipped by case
    break;
case 2:
    ...

Comme mentionné précédemment, une bonne solution consiste à utiliser des accolades pour créer une portée pour votre cas.

MrZebra
la source
1
Monsieur 32, vous avez mal compris votre erreur: oui, cela ne va pas se compiler, mais pas parce que vous déclarez une variable à l'intérieur d'un commutateur. L'erreur est due au fait que vous essayez de déclarer une variable après une instruction, ce qui est illégal en C.
MrZebra
1
Maintenant, un jour qui est légal dans c90 et une version plus récente de c
Jeegar Patel
12

Mon astuce maléfique préférée est d'utiliser un if (0) pour sauter une étiquette de cas indésirable.

switch(val)
{
case 0:
// Do something
if (0) {
case 1:
// Do something else
}
case 2:
// Do something in all cases
}

Mais très mal.

Jeremy
la source
Très agréable. Exemple de pourquoi: le cas 0 et le cas 1 pourraient par exemple initialiser une variable différemment qui est ensuite utilisée dans le cas 2.
hlovdal
1
Si vous voulez que le cas 0 et le cas 1 tombent dans le cas 2. (sans que le cas 0 tombe dans le cas 1). Je ne sais pas si c'est vraiment utile, mais ça marche.
Petruza
1
Vous pouvez simplement accéder à l'étiquette requise gotosans
masquer
10

Essaye ça:

switch (val)
{
    case VAL:
    {
        int newVal = 42;
    }
    break;
}
Dan Shield
la source
7

Vous pouvez déclarer des variables dans une instruction switch si vous démarrez un nouveau bloc:

switch (thing)
{ 
  case A:
  {
    int i = 0;  // Completely legal
  }
  break;
}

La raison est liée à l'allocation (et la récupération) d'espace sur la pile pour le stockage des variables locales.

Seb Rose
la source
1
La variable peut être déclarée, mais elle ne peut pas être initialisée. De plus, je suis presque sûr que le problème ne se rapporte en aucun cas à la pile et aux variables locales.
Richard Corden
6

Considérer:

switch(val)
{
case VAL:
   int newVal = 42;
default:
   int newVal = 23;
}

En l'absence d'instructions break, newVal est parfois déclaré deux fois, et vous ne savez pas si c'est le cas jusqu'à l'exécution. Je suppose que la limitation est due à ce genre de confusion. Quelle serait la portée de newVal? La convention dicterait que ce serait l'ensemble du bloc de commutation (entre les accolades).

Je ne suis pas programmeur C ++, mais en C:

switch(val) {
    int x;
    case VAL:
        x=1;
}

Fonctionne bien. Déclarer une variable à l'intérieur d'un bloc de commutation est très bien. Déclarer après un garde de cas ne l'est pas.

svelte
la source
3
@ Mr.32: en fait, votre exemple montre qu'un printf n'est pas exécuté, mais dans ce cas, l'int x n'est pas une instruction mais une déclaration, le x est déclaré, un espace est réservé à chaque fois que l'environnement de fonction est empilé, voir: codepad.org/4E9Zuz1e
Petruza
Je m'attendais à trouver cela en lisant le titre de la question, car la question ne concerne pas la déclaration de variables dans les étiquettes "case:", mais dans les instructions switch. Et vous seul (et VictorH, en soulignant votre réponse) avez réellement parlé des variables dans les instructions switch.
cesse le
4

La section entière du commutateur est un contexte de déclaration unique. Vous ne pouvez pas déclarer une variable dans une instruction case comme celle-ci. Essayez plutôt ceci:

switch (val)  
{  
case VAL:
{
  // This will work
  int newVal = 42;
  break;
}
case ANOTHER_VAL:  
  ...
  break;
}

la source
La variable peut être déclarée, mais elle ne peut pas être initialisée.
Richard Corden
@Richard Corden Je suis convaincu que l'initialisation fonctionnera. Affirmez-vous toujours qu'il ne peut pas être initialisé?
chux
3

Si votre code dit "int newVal = 42", vous vous attendez raisonnablement à ce que newVal ne soit jamais non initialisé. Mais si vous passez au-dessus de cette déclaration (ce que vous faites), c'est exactement ce qui se passe - newVal est dans le champ d'application mais n'a pas été attribué.

Si c'est ce que vous vouliez vraiment faire, le langage nécessite de le rendre explicite en disant "int newVal; newVal = 42;". Sinon, vous pouvez limiter la portée de newVal au cas unique, ce qui correspond plus probablement à ce que vous vouliez.

Cela peut clarifier les choses si vous considérez le même exemple mais avec "const int newVal = 42;"


la source
3

Je voulais juste souligner mince de points . Une construction de commutateur crée une portée entière de premier ordre pour les citoyens. Il est donc possible de déclarer (et d'initialiser) une variable dans une instruction switch avant la première étiquette de cas, sans paire de crochets supplémentaire:

switch (val) {  
  /* This *will* work, even in C89 */
  int newVal = 42;  
case VAL:
  newVal = 1984; 
  break;
case ANOTHER_VAL:  
  newVal = 2001;
  break;
}
VictorH
la source
-1 ici int newVal = 42; ne sera jamais exécuté. voir ce codepad.org/PA1quYX3
Jeegar Patel
4
la déclaration int newVal sera exécutée, mais pas l' = 42affectation.
Petruza
3

Jusqu'à présent, les réponses ont été pour C ++.

Pour C ++, vous ne pouvez pas sauter une initialisation. Vous pouvez le faire en C. Cependant, en C, une déclaration n'est pas une instruction, et les étiquettes de cas doivent être suivies d'instructions.

Donc, C valide (mais moche), C ++ invalide

switch (something)
{
  case 1:; // Ugly hack empty statement
    int i = 6;
    do_stuff_with_i(i);
    break;
  case 2:
    do_something();
    break;
  default:
    get_a_life();
}

Inversement, en C ++, une déclaration est une instruction, donc ce qui suit est C ++ valide, C non valide

switch (something)
{
  case 1:
    do_something();
    break;
  case 2:
    int i = 12;
    do_something_else();
}
Peter
la source
1
Le deuxième exemple n'est PAS valide C ++ (test avec vc2010 et gcc 4.6.1 C ++ ne permet pas de sauter la partie d'initialisation. Le message d'erreur gcc est: initialisation croisée de 'int i'
zhaorufei
3

Intéressant que ce soit bien:

switch (i)  
{  
case 0:  
    int j;  
    j = 7;  
    break;  

case 1:  
    break;
}

... mais ce n'est pas:

switch (i)  
{  
case 0:  
    int j = 7;  
    break;  

case 1:  
    break;
}

Je comprends qu'un correctif est assez simple, mais je ne comprends pas encore pourquoi le premier exemple ne dérange pas le compilateur. Comme évoqué précédemment (2 ans plus tôt hehe), la déclaration n'est pas la cause de l'erreur, même malgré la logique. L'initialisation est le problème. Si la variable est initialisée et déclarée sur les différentes lignes, elle se compile.

Dan
la source
1
Le premier n'est pas correct sur gcc 4.2: "erreur: expression attendue avant 'int'". Comme Peter et Mr.32 le disent, "cas 0:; int j; ..." et "cas 0:; int j = 7; ..." fonctionnent tous les deux. Le problème en C est simplement que "case <label>: déclaration" n'est pas une syntaxe C valide.
dubiousjim
3

J'ai écrit cette réponse à l'origine pour cette question . Cependant, quand j'ai fini, j'ai trouvé que la réponse était fermée. Je l'ai donc posté ici, peut-être que quelqu'un qui aime les références à la norme le trouvera utile.

Code original en question:

int i;
i = 2;
switch(i)
{
    case 1: 
        int k;
        break;
    case 2:
        k = 1;
        cout<<k<<endl;
        break;
}

Il y a en fait 2 questions:

1. Pourquoi puis-je déclarer une variable après l' caseétiquette?

C'est parce que dans C ++, l'étiquette doit être en forme:

N3337 6.1 / 1

label-statement:

...

  • attribut-spécificateur-seqopt case constant-expression :statement

...

Et dans la C++ déclaration, la déclaration est également considérée comme une déclaration (par opposition à C):

N3337 6/1:

déclaration :

...

déclaration-déclaration

...

2. Pourquoi puis-je sauter par-dessus la déclaration de variable et ensuite l'utiliser?

Parce que: N3337 6.7 / 3

Il est possible de transférer dans un bloc, mais pas d'une manière qui contourne les déclarations avec l'initialisation . Un programme qui saute (Le transfert de la condition d'une instruction switch à une étiquette de cas est considéré comme un saut à cet égard.)

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

Depuis kest de type scalaire , et n'est pas initialisé au moment de la déclaration, il est possible de sauter par-dessus. C'est sémantiquement équivalent:

goto label;

int x;

label:
cout << x << endl;

Cependant cela ne serait pas possible, si a xété initialisé au moment de la déclaration:

 goto label;

    int x = 58; //error, jumping over declaration with initialization

    label:
    cout << x << endl;
PcAF
la source
1

Les nouvelles variables peuvent être décalarisées uniquement à la portée du bloc. Vous devez écrire quelque chose comme ceci:

case VAL:  
  // This will work
  {
  int newVal = 42;  
  }
  break;

Bien sûr, newVal n'a de portée que dans les accolades ...

À la vôtre, Ralph


la source
1

Un switchbloc n'est pas la même chose qu'une succession de if/else ifblocs. Je suis surpris qu'aucune autre réponse ne l'explique clairement.

Considérez cette switchdéclaration:

switch (value) {
    case 1:
        int a = 10;
        break;
    case 2:
        int a = 20;
        break;
}

Cela peut être surprenant, mais le compilateur ne le considérera pas comme un simple if/else if. Il produira le code suivant:

if (value == 1)
    goto label_1;
else if (value == 2)
    goto label_2;
else
    goto label_end;

{
label_1:
    int a = 10;
    goto label_end;
label_2:
    int a = 20; // Already declared !
    goto label_end;
}

label_end:
    // The code after the switch block

Les caseinstructions sont converties en étiquettes, puis appelées avec goto. Les crochets créent une nouvelle portée et il est facile de voir maintenant pourquoi vous ne pouvez pas déclarer deux variables avec le même nom dans unswitch bloc.

Cela peut sembler étrange, mais il est nécessaire de prendre en charge la fonction fallthrough (c'est-à-dire de ne pas utiliser breakpour laisser l'exécution continuer jusqu'à la suivante case).

Dalmas
la source
0

Je crois que le problème est que la déclaration a été ignorée et que vous avez essayé d'utiliser le var ailleurs, elle ne serait pas déclarée.

William Keller
la source
0

newVal existe dans toute l'étendue du commutateur mais n'est initialisé que si le membre VAL est touché. Si vous créez un bloc autour du code dans VAL, cela devrait être OK.

marijne
la source
0

C ++ Standard a: Il est possible de transférer dans un bloc, mais pas d'une manière qui contourne les déclarations avec initialisation. Un programme qui passe d'un point où une variable locale avec une durée de stockage automatique n'est pas dans la portée à un point où elle est dans la portée est mal formé sauf si la variable a le type POD (3.9) et est déclarée sans initialiseur (8.5).

Le code pour illustrer cette règle:

#include <iostream>

using namespace std;

class X {
  public:
    X() 
    {
     cout << "constructor" << endl;
    }
    ~X() 
    {
     cout << "destructor" << endl;
    }
};

template <class type>
void ill_formed()
{
  goto lx;
ly:
  type a;
lx:
  goto ly;
}

template <class type>
void ok()
{
ly:
  type a;
lx:
  goto ly;
}

void test_class()
{
  ok<X>();
  // compile error
  ill_formed<X>();
}

void test_scalar() 
{
  ok<int>();
  ill_formed<int>();
}

int main(int argc, const char *argv[]) 
{
  return 0;
}

Le code pour montrer l'effet d'initialisation:

#include <iostream>

using namespace std;

int test1()
{
  int i = 0;
  // There jumps fo "case 1" and "case 2"
  switch(i) {
    case 1:
      // Compile error because of the initializer
      int r = 1; 
      break;
    case 2:
      break;
  };
}

void test2()
{
  int i = 2;
  switch(i) {
    case 1:
      int r;
      r= 1; 
      break;
    case 2:
      cout << "r: " << r << endl;
      break;
  };
}

int main(int argc, const char *argv[]) 
{
  test1();
  test2();
  return 0;
}
Jingguo Yao
la source
0

Il semble que des objets anonymes peuvent être déclarés ou créés dans une instruction de cas de commutateur pour la raison qu'ils ne peuvent pas être référencés et en tant que tels ne peuvent pas passer au cas suivant. Considérez que cet exemple se compile sur GCC 4.5.3 et Visual Studio 2008 (cela pourrait être un problème de conformité, alors les experts doivent peser)

#include <cstdlib>

struct Foo{};

int main()
{
    int i = 42;

    switch( i )
    {
    case 42:
        Foo();  // Apparently valid
        break;

    default:
        break;
    }
    return EXIT_SUCCESS;
}
Olumide
la source
Si vous allez voter contre, veuillez expliquer pourquoi. Je suis curieux de savoir pourquoi la création d'un objet anonyme semble être une exemption.
Olumide
1
pas un DV, mais: toute la question concerne la déclaration / portée des variables nommées. Un temporaire (un "objet anonyme" n'est pas un terme) n'est pas une variable nommée, ni une déclaration, ni soumis à la portée (sauf s'il est lié à une constréférence avec une portée propre). C'est une expression qui vit et meurt dans sa déclaration (où que ce soit). Par conséquent, c'est totalement hors de propos.
underscore_d
Foo();n'est pas une déclaration; la question concerne les déclarations.
MM