2008-10-31 5 views
110

fait une déclaration simple interrupteurPourquoi l'instruction switch a-t-elle été conçue pour nécessiter une pause?

switch (int) 
{ 
    case 1 : 
    { 
     printf("1\n"); 
     break; 
    } 

    case 2 : 
    { 
     printf("2\n"); 
    } 

    case 3 : 
    { 
     printf("3\n"); 
    } 
} 

L'absence d'une déclaration de rupture dans le cas 2, implique que l'exécution se poursuivra dans le code pour le cas 3. Ce n'est pas un accident; il a été conçu de cette façon. Pourquoi ces décisions ont-elles été prises? Quel avantage cela apporte-t-il par rapport à une sémantique de rupture automatique pour les blocs? Quelle était la raison?

Répondre

134

Beaucoup de réponses semblent se concentrer sur la capacité de tomber à travers comme raison pour exiger l'instruction break.

Je crois que c'était simplement une erreur, en grande partie parce que quand C a été conçu il n'y avait pas autant d'expérience avec la façon dont ces constructions seraient utilisées.

Peter Van der Linden fait le cas dans son livre "Programmation Expert C":

Nous avons analysé les sources du compilateur C Sun pour voir à quelle fréquence la valeur par défaut chute a été utilisé par. La partie antérieure du compilateur Sun ANSI C comporte 244 instructions de commutation , chacune ayant une moyenne de de sept cas. Fall through se produit dans seulement 3% de tous ces cas.

En d'autres termes, le comportement normal du commutateur est incorrect 97% du temps. Il est non seulement dans un compilateur - au contraire , où tombent à travers a été utilisé dans cette analyse, il a été souvent des situations qui se produisent plus fréquemment dans un compilateur que dans d'autres logiciels, par exemple, lors de la compilation des opérateurs que peut avoir une ou deux opérandes:

switch (operator->num_of_operands) { 
    case 2: process_operand(operator->operand_2); 
       /* FALLTHRU */ 

    case 1: process_operand(operator->operand_1); 
    break; 
} 

chute des cas par est si largement reconnu comme un défaut qu'il ya même une convention de commentaire particulier, ci-dessus, qui indique les peluches «c'estEn réalité, l'un de ces 3% de cas où était tombé était souhaité. "

Je pense qu'il était une bonne idée pour C# d'exiger une instruction de saut explicite à la fin de chaque bloc de cas (tout en permettant à plusieurs étiquettes de cas pour être empilés - tant qu'il n'y a qu'un seul bloc de déclarations) . En C# vous pouvez toujours avoir un cas à travers un autre - il vous suffit de rendre la chute explicite en sautant à l'affaire suivante en utilisant un goto.

Dommage que Java n'ait pas saisi l'occasion de rompre avec la sémantique C.

+0

En effet, je pense qu'ils sont allés pour la simplicité de la mise en œuvre. D'autres langages prenaient en charge des cas plus complexes (plages, valeurs multiples, chaînes de caractères ...) au détriment, peut-être, de l'efficacité. – PhiLho

+0

Java ne voulait probablement pas casser ses habitudes et semer la confusion. Pour un comportement différent, ils devraient utiliser une sémantique différente. Les concepteurs Java ont perdu un certain nombre d'opportunités de se détacher de C, de toute façon. – PhiLho

+0

@PhiLho - Je pense que vous êtes probablement le plus proche de la vérité avec "simplicité de mise en œuvre". –

18

Si les cas étaient conçus pour se rompre implicitement, vous ne pourriez pas avoir de retard.

case 0: 
case 1: 
case 2: 
    // all do the same thing. 
    break; 
case 3: 
case 4: 
    // do something different. 
    break; 
default: 
    // something else entirely. 

Si le commutateur était conçu pour exploser implicitement après chaque cas, vous n'auriez pas le choix. La structure de l'interrupteur a été conçue de manière à être plus flexible.

+8

Vous pouvez imager un commutateur qui casse implicitement mais avec un mot clé "fallthrough". Bizarre, mais réalisable. – dmckee

+0

Comment cela serait-il mieux? J'imagine une déclaration de cas de travailler de cette façon plus souvent que "bloc de code par cas" ... c'est un bloc if/then/else. – billjamesdev

+0

dernier commentaire @dmckee – billjamesdev

3

Il élimine la duplication de code lorsque plusieurs cas doivent exécuter le même code (ou le même code en séquence). Comme au niveau de la langue de l'assembleur, peu importe si vous faites une pause entre chaque cas ou non, il n'y a pas de surcharge pour les cas de figure, alors pourquoi ne pas les autoriser car ils offrent des avantages significatifs dans certains cas.

14

Les instructions case d'une instruction switch sont simplement des étiquettes.

Lorsque vous activez une valeur, l'instruction switch fait goto à l'étiquette avec la valeur correspondante. Cela signifie que la rupture est nécessaire pour éviter de passer au code sous l'étiquette suivante.

Pour la raison pourquoi il a été implémenté de cette façon - la nature d'une instruction switch peut être utile dans certains scénarios. Par exemple:

case optionA: 
    // optionA needs to do its own thing, and also B's thing. 
    // Fall-through to optionB afterwards. 
    // Its behaviour is a superset of B's. 
case optionB: 
    // optionB needs to do its own thing 
    // Its behaviour is a subset of A's. 
    break; 
case optionC: 
    // optionC is quite independent so it does its own thing. 
    break; 
+2

Il y a deux gros problèmes: 1) Oublier le 'break' où il est nécessaire. 2) Si les instructions de cas ont changé d'ordre, la chute peut entraîner l'exécution d'un mauvais cas. Ainsi, je trouve que la manipulation de C# est bien meilleure (explicite 'goto case' pour fallthrough, sauf pour les labels vides). –

+0

@DanielRose: 1) il y a des façons d'oublier 'break' en C# aussi - le plus simple est quand vous ne voulez pas qu'un' case' fasse quoi que ce soit mais oubliez d'ajouter un 'break' (peut-être vous avez été enveloppé le commentaire explicatif, ou a été appelé sur une autre tâche): l'exécution tombera juste dans le 'cas' ci-dessous. 2) encourager 'goto case' encourage un codage spaghetti non structuré - vous pouvez vous retrouver avec des cycles accidentels (' cas A: ... cas B, cas B: ...; cas A; '), surtout quand les cas sont séparés dans le fichier et difficile à grok en combinaison. En C++, les retombées sont localisées. –

20

Pour mettre en œuvre le dispositif de Duff, évidemment:

dsend(to, from, count) 
char *to, *from; 
int 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); 
    } 
} 
+4

Hehehe. Petit malin. – dmckee

+3

J'aime l'appareil de Duff. Si élégant et méchant vite. – dicroce

+7

ya, mais doit-il apparaître chaque fois qu'il y a une instruction switch sur SO? – billjamesdev

27

Dans beaucoup de façons c est juste une interface propre à idiomes de montage standard. Lors de l'écriture d'un contrôle de flux piloté par une table de saut, le programmeur a le choix de tomber ou de sauter de la "structure de contrôle", et un saut requiert une instruction explicite.

Ainsi, c fait la même chose ...

+1

Je sais que beaucoup de gens disent cela, mais je pense que ce n'est pas l'image complète; C est également souvent une interface laide à l'antipatterns d'assemblage. 1. Laide: les déclarations à la place des expressions. "Déclaration reflète l'utilisation." Copy-paste glorifié comme * le * mécanisme de modularité et de portabilité. Type de système * way * trop compliqué; * encourage * les bugs. 'NULL'. (Suite dans le commentaire suivant.) – Harrison

+0

2. Antipatterns: Ne fournit pas d'union étiquetée «propre», l'un des deux idiomes fondamentaux de la représentation des données de l'assemblage. (Knuth, vol 1, ch 2) Au lieu de cela, "syndicats non étiquetés", un hack non-idiomatique. Ce choix a entravé la façon dont les gens pensent des données pendant des décennies. [Et les chaînes terminées par "NUL" sont à peu près la pire idée jamais.] (Http://queue.acm.org/detail.cfm?id=2010365) – Harrison

+0

@HarrisonKlaperman: Aucune méthode de stockage des chaînes n'est parfaite. La grande majorité des problèmes associés aux chaînes à terminaison nulle ne se produirait pas si les routines acceptant des chaînes acceptaient également un paramètre de longueur maximale et se produiraient avec des routines qui stockent des chaînes de longueur dans des tampons de taille fixe sans accepter de paramètre de taille de tampon . La conception de l'instruction switch où les cas sont simplement des étiquettes peut sembler étrange aux yeux modernes, mais ce n'est pas pire que la conception de la boucle Fortran 'DO'. – supercat

0

Comme beaucoup ici ont indiqué, il est de permettre à un seul bloc de code pour travailler pour plusieurs cas. Ce devrait être une occurrence plus commune pour vos instructions switch que le "bloc de code par cas" que vous spécifiez dans votre exemple.

Si vous avez un bloc de code par cas sans erreur, vous devriez peut-être envisager d'utiliser un bloc if-elseif-else, car cela semblerait plus approprié.

7

Pour permettre des choses comme:

switch(foo) { 
case 1: 
    /* stuff for case 1 only */ 
    if (0) { 
case 2: 
    /* stuff for case 2 only */ 
    } 
    /* stuff for cases 1 and 2 */ 
case 3: 
    /* stuff for cases 1, 2, and 3 */ 
} 

Pensez le mot-clé case comme une étiquette goto et il vient beaucoup plus naturellement.

+0

Aller à l'aide de l'analogie, thx! – Nils

+7

Que 'if (0)' à la fin du premier cas est mauvais, et ne devrait être utilisé que si l'objectif est d'obscurcir le code. –

+8

Toute cette réponse était un exercice d'être mauvais, je pense. :-) –

Questions connexes