Pourquoi l'instruction switch a-t-elle été conçue pour nécessiter une pause?

139

Étant donné une simple instruction switch

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

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

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

L'absence d'une instruction break dans le cas 2, implique que l'exécution se poursuivra à l'intérieur du 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 justification?

EvilTeach
la source

Réponses:

150

De nombreuses réponses semblent se concentrer sur la capacité à échouer comme raison d'exiger la breakdéclaration.

Je pense que c'était simplement une erreur, en grande partie parce que lorsque 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 en fait la démonstration dans son livre "Expert C Programming":

Nous avons analysé les sources du compilateur Sun C pour voir à quelle fréquence la chute par défaut était utilisée. Le serveur frontal du compilateur Sun ANSI C comporte 244 instructions de commutation, chacune contenant en moyenne sept cas. La chute se produit dans seulement 3% de tous ces cas.

En d'autres termes, le comportement normal du commutateur est erroné 97% du temps. Ce n'est pas seulement dans un compilateur - au contraire, là où la chute a été utilisée dans cette analyse, c'était souvent pour des situations qui se produisent plus fréquemment dans un compilateur que dans d'autres logiciels, par exemple, lors de la compilation d'opérateurs qui peuvent avoir un 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;
}

La chute de cas est si largement reconnue comme un défaut qu'il y a même une convention spéciale de commentaire, illustrée ci-dessus, qui dit à la charpie "c'est vraiment l'un de ces 3% des cas où la chute était souhaitée."

Je pense que c'était une bonne idée pour C # d'exiger une instruction de saut explicite à la fin de chaque bloc de cas (tout en permettant l'empilement de plusieurs étiquettes de cas - tant qu'il n'y a qu'un seul bloc d'instructions). En C #, vous pouvez toujours faire passer un cas à un autre - il vous suffit de rendre explicite la chute en passant au cas suivant en utilisant un goto.

C'est dommage que Java n'ait pas profité de l'occasion pour rompre avec la sémantique C.

Michael Burr
la source
En effet, je pense qu'ils ont opté pour la simplicité de mise en œuvre. Certains autres langages supportaient des cas plus sophistiqués (plages, valeurs multiples, chaînes ...) au prix, peut-être, de l'efficacité.
PhiLho
Java ne voulait probablement pas briser les habitudes et semer la confusion. Pour un comportement différent, ils devraient utiliser une sémantique différente. De toute façon, les concepteurs Java ont perdu un certain nombre d'opportunités de rompre avec C.
PhiLho
@PhiLho - Je pense que vous êtes probablement le plus proche de la vérité avec la "simplicité de mise en œuvre".
Michael Burr
Si vous utilisez des sauts explicites via GOTO, n'est-il pas aussi productif d'utiliser simplement une série d'instructions IF?
DevinB
3
même Pascal implémente leur interrupteur sans interruption. comment le compilateur-codeur C pourrait-il ne pas y penser @@
Chan Le
30

À bien des égards, c est juste une interface claire pour les idiomes d'assemblage standard. Lors de l'écriture d'un contrôle de flux piloté par une table de saut, le programmeur a le choix de passer ou de sauter hors de la "structure de contrôle", et un saut en sortie nécessite une instruction explicite.

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

dmckee --- chaton ex-modérateur
la source
1
Je sais que beaucoup de gens disent cela, mais je pense que ce n'est pas une image complète; C est aussi souvent une interface moche avec les antipatterns d'assemblage. 1. Moche: déclarations au lieu d'expressions. "La déclaration reflète l'utilisation." Glorifié copier-coller comme le mécanisme de la modularité et la portabilité. Type de système façon trop compliquée; encourage les bugs. NULL. (Suite dans le prochain commentaire.)
Harrison
2. Antipatterns: ne fournit pas des unions étiquetées "propres", l'un des deux idiomes fondamentaux de représentation de données de l'assemblage. (Knuth, vol 1, ch 2) Au lieu de cela, des «unions non marquées», un hack non idiomatique. Ce choix a entravé la façon dont les gens perçoivent les données pendant des décennies. Et les NULchaînes terminées sont à peu près la pire idée qui soit.
Harrison
@HarrisonKlaperman: Aucune méthode de stockage de chaînes n'est parfaite. La grande majorité des problèmes associés aux chaînes à terminaison nulle ne se produiraient pas si les routines qui acceptaient des chaînes acceptaient également un paramètre de longueur maximale, et se produiraient avec des routines qui stockent des chaînes marquées par la longueur dans des tampons de taille fixe sans accepter un paramètre de taille de tampon . La conception de l'instruction de commutation où les boîtiers sont simplement des étiquettes peut sembler étrange aux yeux modernes, mais ce n'est pas pire que la conception de la DOboucle Fortran .
supercat du
Si je décide d'écrire une table de saut dans l'assemblage, je prendrai la valeur de cas, et la transformerai comme par magie en indice de la table de saut, passerai à cet emplacement et exécuterai le code. Après cela, je ne passerai pas au cas suivant. Je vais sauter à une adresse uniforme qui est la sortie de tous les cas. L'idée que je sauterais ou tomberais dans le corps du cas suivant est ridicule. Il n'y a aucun cas d'utilisation pour ce modèle.
EvilTeach
2
Alors que de nos jours, les gens sont davantage utilisés pour nettoyer et auto-protéger les idos et les fonctionnalités du langage qui s'empêchent de se tirer dans le pied, il s'agit d'une réminence d'une région où les octets avaient coûté cher (C a commencé avant 1970). si votre code doit tenir dans les 1024 octets, vous ferez l'expérience de la nécessité de réutiliser des fragments de code. La réutilisation du code en commençant à différents points d'entrée partageant la même extrémité est un mécanisme pour y parvenir.
rpy
23

Pour implémenter 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);
    }
}
FlySwat
la source
3
J'adore l'appareil de Duff. Si élégant et rapide.
dicroce
7
oui, mais doit-il apparaître à chaque fois qu'il y a une instruction switch sur SO?
billjamesdev
Vous avez manqué deux accolades fermantes ;-).
Toon Krijthe
18

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

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 éclater implicitement après chaque cas, vous n'auriez pas le choix. La structure du boîtier de commutation a été conçue de manière à être plus flexible.

Bill le lézard
la source
12
Vous pouvez créer une image d'un commutateur qui se rompt implicitement, mais qui a un mot clé "fallthrough". Gênant, mais réalisable.
dmckee --- ex-modérateur chaton
Comment serait-ce mieux? J'imagine qu'une instruction case fonctionne de cette façon plus souvent que "bloc de code par cas" ... c'est un bloc if / then / else.
billjamesdev
J'ajouterais que dans la question, les enclosures de portée {} dans chaque bloc de cas ajoutent à la confusion car cela ressemble au style d'une instruction "while".
Matthew Smith
1
@Bill: Ce serait pire, je pense, mais cela répondrait à la plainte soulevée par Mike B: cette chute (à part plusieurs cas identiques) est un événement rare et ne devrait pas être le comportement par défaut.
dmckee --- ex-moderator chaton
1
J'ai laissé de côté les accolades par souci de brièveté, avec plusieurs déclarations, elles devraient être là. Je conviens que le fallthrough ne doit être utilisé que lorsque les cas sont exactement les mêmes. C'est déroutant comme l'enfer lorsque les cas s'appuient sur des cas précédents en utilisant le fallthrough.
Bill the Lizard
15

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

Lorsque vous activez une valeur, l'instruction switch effectue essentiellement un aller à l'étiquette avec la valeur correspondante.

Cela signifie que la rupture est nécessaire pour éviter de passer au code sous l'étiquette suivante.

Quant à la raison pour laquelle elle a été implémentée de cette manière - la nature fall-through 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;
LéopardPeauPillBox
la source
2
Il y a deux gros problèmes: 1) Oublier l' breakendroit où il est nécessaire. 2) Si l'ordre des instructions de cas est modifié, le basculement peut entraîner l'exécution du mauvais cas. Ainsi, je trouve la gestion de C # bien meilleure (explicite goto casepour le fallthrough, sauf pour les étiquettes de cas vides).
Daniel Rose
@DanielRose: 1) il y a aussi des moyens d'oublier breaken C # - le plus simple est quand vous ne voulez casepas faire quoi que ce soit, mais oubliez d'ajouter un break(peut-être avez-vous été tellement absorbé par le commentaire explicatif, ou avez-vous été appelé sur certains autre tâche): l'exécution tombera simplement en casedessous. 2) encourageant goto caseest encourageant le codage «spaghetti» non structuré - vous pouvez vous retrouver avec des cycles accidentels ( case A: ... goto case B; case B: ... ; goto case A;), surtout lorsque les cas sont séparés dans le fichier et difficiles à combiner. En C ++, le fall-through est localisé.
Tony Delroy
8

Pour autoriser 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 */
}

Considérez le casemot - clé comme une gotoétiquette et cela vient beaucoup plus naturellement.

R .. GitHub STOP AIDING ICE
la source
8
Cela if(0)à la fin du premier cas est maléfique et ne devrait être utilisé que si l'objectif est d'obscurcir le code.
Daniel Rose
11
Toute cette réponse était un exercice pour être mauvais, je pense. :-)
R .. GitHub STOP AIDER ICE
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).

Étant donné qu'au niveau du langage d'assemblage, peu importe si vous coupez entre chacun d'eux ou non, il n'y a de toute façon aucune surcharge pour les cas de chute, alors pourquoi ne pas les autoriser car ils offrent des avantages significatifs dans certains cas.

Greg Rogers
la source
2

Je suis tombé sur un cas d'assignation de valeurs dans des vecteurs à des structures: cela devait être fait de telle manière que si le vecteur de données était plus court que le nombre de membres de données dans la structure, le reste des membres resterait dans leur valeur par défaut. Dans ce cas, l'omission breakétait très utile.

switch (nShorts)
{
case 4: frame.leadV1    = shortArray[3];
case 3: frame.leadIII   = shortArray[2];
case 2: frame.leadII    = shortArray[1];
case 1: frame.leadI     = shortArray[0]; break;
default: TS_ASSERT(false);
}
Martti K
la source
0

Comme beaucoup l'ont spécifié ici, il s'agit de permettre à un seul bloc de code de fonctionner pour plusieurs cas. Ce devrait être une occurrence plus courante 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 échec, vous devriez peut-être envisager d'utiliser un bloc if-elseif-else, car cela semblerait plus approprié.

billjamesdev
la source