319 votes

Erreur : Sauter à l'étiquette de cas dans l'instruction switch

J'ai écrit un programme qui nécessite l'utilisation d'instructions switch, cependant lors de la compilation cela montre:

Erreur : Sauter à l'étiquette du case.

Pourquoi cela fait-il ça?

#include <iostream>
int main() 
{
    int choice;
    std::cin >> choice;
    switch(choice)
    {
      case 1:
        int i=0;
        break;
      case 2: // erreur ici 
    }
}

1 votes

Quel code essayez-vous de compiler? Quel compilateur utilisez-vous? Avez-vous placé chaque bloc case entre accolades?

5 votes

C'est un message d'erreur étonnamment compliqué

598voto

JohannesD Points 4935

Le problème est que les variables déclarées dans un case sont encore visibles dans les case suivants à moins qu'un bloc explicite { } ne soit utilisé, mais elles ne seront pas initialisées car le code d'initialisation appartient à un autre case.

Dans le code suivant, si foo est égal à 1, tout va bien, mais s'il est égal à 2, nous utiliserons accidentellement la variable i qui existe mais contient probablement des données incorrectes.

switch(foo) {
  case 1:
    int i = 42; // i existe jusqu'à la fin du switch
    dostuff(i);
    break;
  case 2:
    dostuff(i*2); // i est *aussi* accessible ici, mais n'est pas initialisé !
}

Envelopper le cas dans un bloc explicite résout le problème :

switch(foo) {
  case 1:
    {
        int i = 42; // i n'existe que dans les { }
        dostuff(i);
        break;
    }
  case 2:
    dostuff(123); // Maintenant, vous ne pouvez pas utiliser i accidentellement
}

Modifier

Pour aller plus loin, les déclarations switch sont juste une manière particulièrement élaborée d'utiliser un goto. Voici un morceau de code analogue présentant le même problème mais utilisant un goto à la place d'un switch :

int main() {
    if(rand() % 2) // Lancer une pièce
        goto end;

    int i = 42;

  end:
    // Nous avons soit sauté la déclaration de i, soit pas,
    // mais dans les deux cas la variable i existe ici, car
    // les portées de variables sont résolues à la compilation.
    // Que le code d'initialisation ait été exécuté ou non dépend
    // de la valeur renvoyée par rand.
    std::cout << i;
}

3 votes

Voir ce rapport de bogue LLVM corrigé pour d'autres explications: llvm.org/bugs/show_bug.cgi?id=7789

101voto

Mahesh Points 20994

Déclarer de nouvelles variables dans les instructions case est ce qui cause des problèmes. Enfermer toutes les instructions case dans des {} limitera la portée des variables nouvellement déclarées au cas actuellement en cours d'exécution, ce qui résout le problème.

switch(choice)
{
    case 1: {
       // .......
    }break;
    case 2: {
       // .......
    }break;
    case 3: {
       // .......
    }break;
}

1 votes

Instructions de nettoyage et de réparation

0 votes

Y aura-t-il un problème si je mets l'instruction break à l'intérieur des accolades?

1 votes

Je rajoute toujours des accolades, toutes sortes de problèmes de portée étranges peuvent se produire si vous ne le faites pas. @VishalSharma il est correct d'avoir l'instruction break à l'intérieur des accolades

17voto

Ciro Santilli Points 3341

Norme C++11 sur le saut de certaines initialisations

JohannesD a donné une explication, maintenant pour les normes.

La brouillon de norme C++11 N3337 6.7 "Déclaration de variable" dit :

3 Il est possible de sauter dans un bloc, mais pas de manière à contourner les déclarations avec initialisation. Un programme qui saute (87) d'un point où une variable avec une durée de stockage automatique n'est pas dans le contexte à un point où elle est dans le contexte est mal formé sauf si la variable a un type scalaire, un type de classe avec un constructeur trivial par défaut et un destructeur trivial, une version cv-qualifiée de l'un de ces types, ou un tableau de l'un des types précédents et est déclaré sans initialisateur (8.5).

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

[ Exemple:

void f() {
   // ...
  goto lx;    // mal formé: saut dans le contexte de a
  // ...
ly:
  X a = 1;
  // ...
lx:
  goto ly;    // OK, le saut implique le destructeur
              // appel pour a suivi de la construction
              // à nouveau immédiatement après l'étiquette ly
}

— fin de l'exemple ]

À partir de GCC 5.2, le message d'erreur dit maintenant :

traverse l'initialisation de

C

C le permet : c99 goto past initialization

La brouillon de norme C99 N1256 Annexe I "Avertissements courants" dit :

2 Un bloc avec initialisation d'un objet qui a une durée de stockage automatique est sauté dans

14voto

Fabio Turati Points 2527

La réponse de JohannesD est correcte, mais je trouve qu'elle n'est pas tout à fait claire sur un aspect du problème.

L'exemple qu'il donne déclare et initialise la variable i dans le cas 1, puis essaie de l'utiliser dans le cas 2. Son argument est que si le switch allait directement au cas 2, i serait utilisé sans être initialisé, et c'est pourquoi il y a une erreur de compilation. À ce stade, on pourrait penser qu'il n'y aurait pas de problème si les variables déclarées dans un cas n'étaient jamais utilisées dans d'autres cas. Par exemple:

switch(choice) {
    case 1:
        int i = 10; // i n'est jamais utilisé en dehors de ce cas
        printf("i = %d\n", i);
        break;
    case 2:
        int j = 20; // j n'est jamais utilisé en dehors de ce cas
        printf("j = %d\n", j);
        break;
}

On pourrait s'attendre à ce que ce programme compile, puisque à la fois i et j sont utilisés uniquement à l'intérieur des cas qui les déclarent. Malheureusement, en C++ cela ne compile pas : comme l'a expliqué Ciro Santilli 包子露宪 六四事件 法轮功, nous ne pouvons tout simplement pas sauter à case 2:, car cela passerait à côté de la déclaration avec l'initialisation de i, et même si case 2 n'utilise pas du tout i, cela est toujours interdit en C++.

De manière intéressante, avec quelques ajustements (un #ifdef pour #include l'en-tête appropriée, et un point-virgule après les labels, car les labels ne peuvent être suivis que d'instructions, et les déclarations ne comptent pas comme instructions en C), ce programme compile en C :

// Désactive l'avertissement émis par MSVC sur le fait que scanf est obsolète
#ifdef _MSC_VER
#define _CRT_SECURE_NO_WARNINGS
#endif

#ifdef __cplusplus
#include 
#else
#include 
#endif

int main() {

    int choice;
    printf("Veuillez entrer 1 ou 2 : ");
    scanf("%d", &choice);

    switch(choice) {
        case 1:
            ;
            int i = 10; // i n'est jamais utilisé en dehors de ce cas
            printf("i = %d\n", i);
            break;
        case 2:
            ;
            int j = 20; // j n'est jamais utilisé en dehors de ce cas
            printf("j = %d\n", j);
            break;
    }
}

Grâce à un compilateur en ligne comme http://rextester.com, vous pouvez rapidement essayer de le compiler en tant que C ou C++, en utilisant MSVC, GCC ou Clang. En C, cela fonctionne toujours (n'oubliez pas de définir STDIN !), en C++ aucun compilateur ne l'accepte.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X