Considérez l'énumération suivante et l'instruction switch:
typedef enum {
MaskValueUno,
MaskValueDos
} testingMask;
void myFunction(testingMask theMask) {
switch (theMask) {
case MaskValueUno: {}// deal with it
case MaskValueDos: {}// deal with it
default: {} //deal with an unexpected or uninitialized value
}
};
Je suis un programmeur Objective-C, mais je l'ai écrit en C pur pour un public plus large.
Clang / LLVM 4.1 avec -Weverything me met en garde sur la ligne par défaut:
Label par défaut dans le commutateur qui couvre toutes les valeurs d'énumération
Maintenant, je peux en quelque sorte comprendre pourquoi c'est là: dans un monde parfait, les seules valeurs entrant dans l'argument theMask
seraient dans l'énumération, aucun paramètre par défaut n'est nécessaire. Mais que se passe-t-il si un piratage arrive et jette un int non initialisé dans ma belle fonction? Ma fonction sera remplacée par une bibliothèque vide et je n’ai aucun contrôle sur ce qui pourrait y entrer. L'utilisation default
est une manière très soignée de gérer cela.
Pourquoi les dieux de la LLVM estiment-ils que ce comportement est indigne de leur appareil infernal? Devrais-je être précédé d'une instruction if pour vérifier l'argument?
"Pro tip: Try setting the -Weverything flag and checking the “Treat Warnings as Errors” box your build settings. This turns on Hard Mode in Xcode."
.-Weverything
Cela peut être utile, mais veillez à ne pas trop muter votre code pour pouvoir le gérer. Certains de ces avertissements ne sont pas seulement inutiles, ils sont contre-productifs et il vaut mieux les désactiver. (En effet, c'est le cas d'utilisation de-Weverything
: commencez par l'activer et désactivez ce qui n'a pas de sens.)Réponses:
Voici une version qui ne souffre ni de la signalisation du problème ni de celle contre laquelle vous vous protégez:
Killian a déjà expliqué pourquoi clang émet l’avertissement: si vous étendiez l’énum, vous tomberiez dans le cas par défaut, ce qui n’est probablement pas ce que vous voulez. La bonne chose à faire est de supprimer le cas par défaut et d'obtenir des avertissements pour les conditions non gérées .
Vous craignez maintenant que quelqu'un puisse appeler votre fonction avec une valeur en dehors de l'énumération. Cela ressemble à un non-respect de la condition préalable de la fonction: il est documenté d'attendre une valeur de l'
testingMask
énumération, mais le programmeur a passé quelque chose d'autre. Faites donc une erreur de programmation en utilisantassert()
(ouNSCAssert()
comme vous avez dit que vous utilisez Objective-C). Faites que votre programme plante avec un message expliquant que le programmeur le fait mal, si le programmeur le fait mal.la source
return;
et ajouter unassert(false);
à la fin (au lieu de me répéter en énumérant les énumérations légales dans une initialeassert()
et dans laswitch
).Avoir une
default
étiquette ici est un indicateur que vous êtes confus quant à ce que vous attendez. Depuis que tu as épuisé tous les possiblesenum
explicitement valeurs , ildefault
est impossible de l'exécuter et vous n'avez pas non plus besoin de cette option pour vous protéger des modifications futures, car si vous étiez étenduenum
, la construction générerait déjà un avertissement.Ainsi, le compilateur remarque que vous avez couvert toutes les bases mais semble penser que vous ne l’avez pas fait, ce qui est toujours un mauvais signe. En faisant un effort minimal pour modifier le
switch
formulaire comme prévu, vous montrez au compilateur que ce que vous semblez faire est ce que vous êtes en train de faire, et vous le savez.la source
Clang est confus, ayant une déclaration par défaut, il y a une pratique parfaite, on l'appelle correcte de programmation défensive et est considérée comme une bonne pratique de programmation (1). Il est largement utilisé dans les systèmes critiques, mais peut-être pas dans la programmation bureautique.
La programmation défensive a pour but de détecter des erreurs inattendues qui, en théorie, ne se produiraient jamais. Une telle erreur inattendue n’est pas nécessairement le programmeur qui donne à la fonction une entrée incorrecte, ni même un "piratage diabolique". Plus probablement, cela pourrait être dû à une variable corrompue: débordement de tampon, débordement de pile, code de bourrage et bogues similaires non liés à votre fonction pourraient en être la cause. Et dans le cas de systèmes embarqués, les variables peuvent éventuellement changer en raison de l'EMI, en particulier si vous utilisez des circuits de RAM externes.
En ce qui concerne ce qui est écrit dans la déclaration par défaut ... si vous soupçonnez que le programme a mal tourné une fois que vous y êtes arrivé, vous avez besoin d’une sorte de traitement des erreurs. Dans de nombreux cas, vous pouvez probablement simplement simplement ajouter une déclaration vide avec un commentaire: "inattendu mais ça ne fait rien", etc., pour montrer que vous avez pensé à la situation improbable.
(1) MISRA-C: 2004 15.3.
la source
-Weverything
active tous les avertissements, pas une sélection appropriée à un cas d’utilisation particulier. Ne correspond pas à votre goût n'est pas la même chose que la confusion.Mieux encore:
Cela est moins sujet aux erreurs lors de l'ajout d'éléments à l'énumération. Vous pouvez ignorer le test pour> = 0 si vous définissez vos valeurs enum non signées. Cette méthode ne fonctionne que si vos valeurs enum ne manquent pas, mais c'est souvent le cas.
la source
Ensuite, vous obtenez un comportement indéfini , et votre
default
volonté n'aura aucun sens. Vous ne pouvez rien faire pour améliorer la situation.Laissez-moi être plus clair. Le moment où quelqu'un passe un non initialisé
int
dans votre fonction, c'est un comportement indéfini. Votre fonction pourrait résoudre le problème de l’arrêt et cela n’aurait aucune importance. C'est UB. Vous ne pouvez rien faire une fois que UB a été appelé.la source
La déclaration par défaut ne serait pas nécessairement utile. Si le commutateur est sur une énumération, toute valeur non définie dans la énumération finira par exécuter un comportement non défini.
Pour tout ce que vous savez, le compilateur peut compiler ce commutateur (avec la valeur par défaut) comme suit:
Une fois que vous avez déclenché un comportement indéfini, vous ne pouvez plus revenir en arrière.
la source
enum
type est identique à la plage de valeurs de son type entier sous-jacent (défini par l'implémentation, il doit donc être autocohérent).Je préfère aussi avoir un
default:
dans tous les cas. Je suis en retard à la fête comme d'habitude, mais ... d'autres pensées que je n'ai pas vues plus haut:-Werror
) provient de-Wcovered-switch-default
(de-Weverything
mais pas-Wall
). Si votre souplesse morale vous permet de désactiver certains avertissements (c.-à-d. Exciser certaines choses de-Wall
ou-Weverything
), envisagez de lancer-Wno-covered-switch-default
(ou de les-Wno-error=covered-switch-default
utiliser-Werror
), et en général-Wno-...
pour d'autres avertissements que vous trouvez désagréables.gcc
(et un comportement plus génériqueclang
), consultez lagcc
page de manuel pour-Wswitch
,-Wswitch-enum
,-Wswitch-default
pour (différent) comportement dans des situations similaires des types énumérés dans les états de commutation.Je n'aime pas non plus cet avertissement conceptuel, ni son libellé; pour moi, les mots de l'avertissement ("étiquette par défaut ... couvre toutes les ... valeurs") suggèrent que le
default:
cas sera toujours exécuté, tel queEn première lecture, ce que je pensais que vous étiez en cours d' exécution dans - que votre
default:
cas serait toujours exécuté parce qu'il n'y a pasbreak;
oureturn;
ou similaire. Ce concept est similaire (à mon oreille) à un autre commentaire (bien qu’exceptionnellement utile) de la part d’une nounouclang
. Sifoo == 1
, les deux fonctions seront exécutées; votre code ci-dessus a ce comportement. C'est-à-dire que vous ne pouvez vous échapper que si vous souhaitez continuer à exécuter le code des cas suivants! Cela ne semble toutefois pas être votre problème.Au risque d’être pédant, quelques autres réflexions pour la complétude:
int
ou quelque chose à cette fonction, qui est explicitement l' intention de consommer votre propre type spécifique, votre compilateur doit vous protéger aussi bien dans cette situation avec un avertissement agressif ou erreur. MAIS ça ne marche pas! (C'est-à-dire, il semble qu'au moinsgcc
etclang
ne fait pas deenum
vérification de type, mais j'ai entendu dire queicc
oui ). Étant donné que vous n'obtenez pas la sécurité de type , vous pouvez obtenir une sécurité de valeur, comme indiqué ci-dessus. Sinon, comme suggéré dans TFA, considérez unstruct
ou quelque chose qui peut fournir une sécurité de type.enum
typeMaskValueIllegal
, et de ne pas soutenir celacase
dans votreswitch
. Cela serait mangé par ledefault:
(en plus de toute autre valeur farfelue)Vive le codage défensif!
la source
Voici une suggestion alternative:
Le PO essaie de se protéger contre le cas où quelqu'un passe dans un
int
endroit où une énumération est attendue. Ou plus probablement, lorsque quelqu'un a lié une ancienne bibliothèque à un programme plus récent en utilisant un en-tête plus récent avec plus de cas.Pourquoi ne pas changer le commutateur pour gérer le
int
cas? Ajouter une distribution devant la valeur dans le commutateur élimine l'avertissement et fournit même un indice sur la raison pour laquelle la valeur par défaut existe.Je trouve cela beaucoup moins désagréable que de
assert()
tester chacune des valeurs possibles ou même de supposer que la plage de valeurs enum est bien ordonnée, de sorte qu'un test plus simple fonctionne. Ceci est juste une façon laide de faire ce que défaut fait précisément et magnifiquement.la source