Pourquoi utiliser apparemment insignifiantes, do-while et if-else dans les macros?
Dans beaucoup de C/C++ macros je vais voir le code de la macro enveloppé dans ce qui semble être un sens do while
boucle. Ici sont des exemples.
#define FOO(X) do { f(X); g(X); } while (0)
#define FOO(X) if (1) { f(X); g(X); } else
Je ne peux pas voir ce que le do while
est en train de faire. Pourquoi ne pas simplement écrire ceci sans elle?
#define FOO(X) f(X); g(X)
Pour l'exemple avec l'autre, je voudrais ajouter une expression de
Rappel que le
void
type à la fin... comme ((void)0).Rappel que le
do while
construire n'est pas compatible avec le retour des déclarations, de sorte que le if (1) { ... } else ((void)0)
construire a plus compatibles avec les usages dans le Standard c Et en C de GNU, vous allez préférer la construction décrite dans ma réponse.OriginalL'auteur jfm3 | 2008-09-30
Vous devez vous connecter pour publier un commentaire.
La
do ... while
etif ... else
sont là pour faire en sorte que d'unepoint-virgule après votre macro signifie toujours la même chose. Disons que vous
avait quelque chose comme votre deuxième macro.
Maintenant, si vous étiez à utiliser
BAR(X);
dans unif ... else
instruction, où les organes de l'instruction if n'ont pas été enveloppé dans des accolades, vous obtiendrez une mauvaise surprise.Le code ci-dessus serait de développer dans
qui est syntaxiquement incorrect, car l'autre n'est plus associé avec le si. Il n'aide pas à envelopper les choses dans des accolades à l'intérieur de la macro, car un point-virgule après les accolades est syntaxiquement incorrect.
Il y a deux façons de régler le problème. La première consiste à utiliser une virgule à la séquence d'instructions à l'intérieur de la macro sans voler il de sa capacité à agir comme une expression.
La version ci-dessus de la barre de
BAR
élargit le code ci-dessus dans ce qui suit, qui est syntaxiquement correct.Cela ne fonctionne pas si, au lieu de
f(X)
vous avez une plus compliqué corps de code qui doit aller dans son propre bloc, disons, par exemple, pour déclarer les variables locales. Dans le cas le plus général, la solution est d'utiliser quelque chose commedo ... while
à cause de la macro à un seul état, qui prend un point-virgule, sans risque de confusion.Vous n'avez pas à utiliser
do ... while
, vous pourriez faire cuire quelque chose avecif ... else
ainsi, bien que lors de laif ... else
se développe à l'intérieur d'unif ... else
il conduit à une "balançant d'autre", qui pourrait faire un existant balançant d'autre problème encore plus difficile à trouver, comme dans le code suivant.Le point est d'utiliser le point-virgule dans les contextes où une balançant point-virgule est erronée. Bien sûr, il pourrait (et devrait) être soutenu à ce point qu'il serait mieux de déclarer
BAR
comme une fonction réelle, pas une macro.En résumé, le
do ... while
est là pour contourner les carences de l'préprocesseur C. Lorsque les guides de style vous dire de licencier le préprocesseur C, c'est le genre de chose qu'il craint.N'est-ce pas un argument solide à toujours utiliser des accolades en si, et pour les états? Si vous faites un point d'honneur à toujours faire cela (comme l'exige MISRA-C, par exemple), le problème décrit ci-dessus s'en va.
Pourquoi est-ce que je me sens pire qu'un programmeur après avoir appris tout cela...
La virgule exemple devrait être
#define BAR(X) (f(X), g(X))
sinon la priorité de l'opérateur peut gâcher la sémantique.bien que les deux de vous et de moi-de-quatre-et-un-moitié-ans-il y a faire un bon point, nous avons de vivre dans le monde réel. Si nous ne pouvons garantir que toutes les
if
déclarations, etc, dans notre code utiliser des accolades, alors l'enveloppant macros comme c'est une manière simple d'éviter les problèmes.OriginalL'auteur jfm3
Les Macros sont des copier/coller des morceaux de texte le pré-processeur va mettre dans le véritable code; la macro de l'auteur espère que le remplacement sera de produire un code valide.
Il y a trois bons "trucs" pour réussir:
L'aide de la macro se comportent comme de véritables code
De code est généralement terminée par un point-virgule. Si l'utilisateur afficher le code de ne pas avoir besoin d'une...
Cela signifie que l'utilisateur s'attend à ce que le compilateur génère une erreur si le point-virgule est absent.
Mais le vrai de vrai bonne raison est que à un certain moment, la macro de l'auteur sera peut-être nécessaire de remplacer la macro avec une véritable fonction (peut-être inline). Afin que la macro doit vraiment se comporter comme un.
Donc, nous devrions avoir une macro besoin de point-virgule.
Produire un code valide
Comme indiqué dans jfm3 réponse, parfois la macro contient plus d'une instruction. Et si la macro est utilisée à l'intérieur d'une instruction if, ce sera problématique:
Cette macro peut être développée comme:
La
g
fonction sera exécutée indépendamment de la valeur debIsOk
.Cela signifie que nous devons avoir à ajouter un champ à la macro:
Produire un code valide 2
Si la macro est quelque chose comme:
Nous pourrions avoir un autre problème dans le code suivant:
Car il permettrait d'augmenter comme:
Ce code ne compile pas, bien sûr. Donc, encore une fois, la solution est d'utiliser un champ d'application:
Le code fonctionne correctement à nouveau.
Combinaison semi-colon + effets de périmètre?
Il y a un C/C++ un langage qui produit cet effet: La boucle do-while:
Le do/while pouvez créer un champ, donc de l'encapsulation de la macro de code, et a besoin d'un point-virgule à la fin, élargissant ainsi dans le code nécessitant qu'un.
Le bonus?
Le compilateur C++ permettra d'optimiser loin la boucle do-while, comme le fait de sa post-condition est fausse est connu au moment de la compilation. Cela signifie qu'une macro comme:
va développer correctement que
et est ensuite compilé et optimisé loin que
Il y a un certain nombre de petits, mais pas tout à fait négligeable des problèmes avec cette réponse. Par exemple:
void doSomething() { int i = 25 ; { int i = x + 1 ; f(i) ; } ; // was MY_MACRO(32) ; }
n'est pas la bonne extension; lax
dans l'expansion devrait être de 32. Un problème plus complexe est qu'est-ce que l'expansion deMY_MACRO(i+7)
. Et l'autre est l'expansion deMY_MACRO(0x07 << 6)
. Il y en a beaucoup c'est bien, mais il y a quelques undotted i et décroise t.J'ai le cas où vous êtes encore ici, et vous n'avez pas compris cela maintenant: vous pouvez échapper à des astérisques et des caractères de soulignement dans les commentaires avec des barres obliques inverses, donc si vous tapez
\_\_LINE\_\_
il rend aussi __LINE__. À mon humble avis, il vaut mieux juste pour l'utilisation de formatage de code pour le code; par exemple,__LINE__
(qui ne nécessite pas de traitement spécial). P. S. je ne sais pas si c'était vrai en 2012; ils ai fait quelques améliorations sur le moteur depuis.En reconnaissant que, dans mon commentaire, c'est six ans de retard, mais la plupart des compilateurs C ne sont pas réellement inline
inline
fonctions (dans la mesure permise par la norme)OriginalL'auteur paercebal
@jfm3 - Vous avez une bonne réponse à la question. Vous pouvez également ajouter que la macro idiome empêche également la peut-être plus dangereux (car il n'y a pas d'erreur) comportement involontaire avec de simples 'si' états:
étend à:
qui est syntaxiquement correct donc il n'y a pas d'erreur de compilation, mais il a probablement pour conséquence involontaire que g() sera toujours appelé.
Ou ils pourraient être donnée à une relance, s'ils travaillent pour un trois-lettre de l'agence et subrepticement d'insérer ce code dans un largement utilisé, programme open source... 🙂
Et ce commentaire me rappelle de l'goto fail ligne dans la récente SSL cert vérification bug trouvé dans Apple Os
OriginalL'auteur
Les réponses ci-dessus, expliquer le sens de ces constructions, mais il y a une différence significative entre les deux qui n'a pas été mentionné. En fait, il y a une raison pour préférer la
do ... while
à laif ... else
construire.Le problème de la
if ... else
construire, c'est qu'il n'a pas force vous afin de mettre le point-virgule. Comme dans ce code:Bien que nous avons laissé de côté le point-virgule (par erreur), le code s'étendre à
et en silence à la compilation (bien que certains compilateurs peuvent émettre un avertissement pour code inaccessible). Mais le
printf
déclaration ne sera jamais exécutée.do ... while
construire n'a pas ce problème, puisque le seul jeton valide après lawhile(0)
est un point-virgule.FOO(1),x++;
qui nous donne à nouveau un faux positif. Utilisez simplementdo ... while
et c'est tout.Documentation de la macro pour éviter le malentendu devrait suffire. Je suis d'accord que
do ... while (0)
est préférable, mais elle a un inconvénient: Unbreak
oucontinue
contrôle ledo ... while (0)
boucle, pas la boucle contenant la macro invocation. Ainsi, leif
truc a encore de la valeur.Je ne vois pas où vous pourriez mettre une
break
ou uncontinue
cela serait considéré comme de l'intérieur de vos macrosdo {...} while(0)
pseudo-boucle. Même dans les paramètre de la macro il serait une erreur de syntaxe.Une autre raison d'utiliser
do { ... } while(0)
au lieu deif whatever
construire, est le idiomatiques nature de celui-ci. Ledo {...} while(0)
construire est répandu, bien connu et très utilisé par de nombreux programmeurs. Sa raison d'être et la documentation est bien connu. Pas tellement pour leif
construire. Il faut donc moins d'effort à analyser lors de la revue de code.J'ai vu des gens écrire des macros qui prennent des blocs de code comme arguments (que je ne recommande pas nécessairement). Par exemple:
#define CHECK(call, onerr) if (0 != (call)) { onerr } else (void)0
. Il peut être utilisé commeCHECK(system("foo"), break;);
, où labreak;
est destiné à se référer à la boucle englobante de laCHECK()
invocation.OriginalL'auteur ybungalobill
Bien qu'il soit prévu que les compilateurs d'optimiser loin le
do { ... } while(false);
boucles, il y a une autre solution qui ne nécessite que de construire. La solution est d'utiliser l'opérateur virgule:ou encore plus exotique:
Alors que cela fonctionne bien avec des instructions distinctes, il ne fonctionnera pas avec les cas où les variables sont construites et utilisées dans le cadre de la
#define
:Avec ce que l'on serait obligé d'utiliser le faire/tout en construire.
merci, depuis l'opérateur virgule ne garantit pas l'ordre d'exécution, cette imbrication est une façon de la faire respecter.
False; l'opérateur virgule est un point de séquence et ainsi ne garantie d'ordre d'exécution. Je soupçonne que vous avez confondu avec la virgule, en fonction de listes d'arguments.
La deuxième exotiques suggestion faite ma journée.
Je voulais juste rajouter que les compilateurs sont obligés de préserver le programme et les comportements observables, optimisant ainsi le faire, alors que l'écart n'est pas une grosse affaire (en supposant que les optimisations du compilateur sont corrects).
OriginalL'auteur Marius
Jens Gustedt de P99 préprocesseur bibliothèque (oui, le fait qu'une telle chose existe soufflé mon esprit trop!) améliore la
if(1) { ... } else
construire dans une petite, mais significative de la voie en définissant les éléments suivants:La raison pour cela est que, contrairement à la
do { ... } while(0)
construire,break
etcontinue
encore travailler à l'intérieur du bloc donné, mais la((void)0)
crée une erreur de syntaxe si le point-virgule est omis après l'appel de macro, qui serait sinon passer au bloc suivant. (Il n'est pas réellement un "balançant d'autre" problème ici, puisque leelse
se lie à la plus procheif
, qui est celui de la macro).Si vous êtes intéressé par le genre de choses qui peut être fait plus ou moins en sécurité avec le préprocesseur C, découvrez la bibliothèque.
Vous utilisez généralement des macros pour créer un environnement, qui est, vous n'utilisez jamais un
break
(oucontinue
) à l'intérieur d'une macro pour contrôler une boucle qui a commencé/terminé à l'extérieur, c'est juste le mauvais style, et des peaux de potentiels points de sortie.Il y a aussi un préprocesseur bibliothèque Boost. Ce qui est hallucinant à ce sujet?
OriginalL'auteur Isaac Schwabacher
Pour certaines raisons, je ne peux pas commenter sur la première réponse...
Certains d'entre vous ont montré des macros avec des variables locales, mais personne ne dit que vous ne pouvez pas utiliser n'importe quel nom dans une macro! Il va mordre à l'utilisateur un jour! Pourquoi? Parce que les arguments d'entrée sont substitués dans votre modèle de macro. Et dans vos exemples de macro que vous avez utilisez probablement le plus couramment utilisé variabled nom je.
Par exemple lorsque la macro suivante
est utilisé dans la fonction suivante
la macro n'utiliserez pas le but de la variable i, qui est déclarée au début de some_func, mais la variable locale, qui est déclarée dans la boucle do ... while de la macro.
Donc, ne jamais utiliser de communes des noms de variable dans une macro!
int __i;
.En fait c'est incorrect et illégal C; principaux traits de soulignement sont réservés pour une utilisation par la mise en œuvre. La raison pour laquelle vous avez vu ce "modèle" est à partir du système de lecture des en-têtes ("l'application"), qui doivent se limiter à cet espace de noms réservés. Pour les applications et les bibliothèques, vous devez choisir votre propre obscur, peu de chances-pour-la collision des noms sans souligne, par exemple,
mylib_internal___i
ou similaire.Vous avez raison, en fait, j'ai lu cela dans une "application", le noyau Linux, mais c'est une exception de toute façon puisqu'il n'utilise pas de bibliothèque standard (techniquement, un "autoportant" C mise en place d'un "hébergé" l'un).
ce n'est pas tout à fait exact: les principaux traits de soulignement suivi d'un capital ou de la deuxième trait de soulignement sont réservés pour la mise en œuvre dans tous les contextes. Principaux traits de soulignement suivie par quelque chose d'autre ne sont pas réservés à une portée locale.
Oui, mais la distinction est suffisamment subtile pour que je trouve qu'il est préférable de dire aux gens tout simplement, ne pas utiliser de tels noms à tous. Les gens qui comprennent la subtilité sans doute déjà savoir que je suis dissimuler les détails. 🙂
OriginalL'auteur Mike Meyer
Explication
do {} while (0)
etif (1) {} else
sont à assurez-vous que la macro est élargi à seulement 1 instruction. Sinon:afficherait:
Et
g(X)
serait exécuté en dehors de laif
de contrôle. Cela est évité lors de l'utilisation dedo {} while (0)
etif (1) {} else
.Meilleure alternative
Avec un GNU déclaration de l'expression (ne faisant pas partie de la norme C), vous avez une meilleure façon que
do {} while (0)
etif (1) {} else
pour résoudre ce problème, en utilisant simplement({})
:Et cette syntaxe est compatible avec les valeurs de retour (à noter que
do {} while (0)
n'est-ce pas), comme dans:OriginalL'auteur Cœur
Je ne pense pas qu'il a été mentionné considérez donc cet
serait traduit en
avis comment
i++
est évalué deux fois par la macro. Cela peut conduire à certains intéressant erreurs.Vrai. Mais concernant le sujet de macros vs fonctions et comment écrire une macro qui se comporte comme une fonction...
Tout comme pour le précédent, celui-ci n'est pas une réponse mais un commentaire. Sur le sujet: c'est pourquoi vous utilisez des trucs qu'une seule fois:
do { int macroname_i = (i); f(macroname_i); g(macroname_i); } while (/* CONSTCOND */ 0)
OriginalL'auteur John Nilsson
J'ai trouvé cette astuce très utile dans les situations où vous avez à séquentiellement processus d'une valeur particulière. À chaque niveau de traitement, si des erreurs ou invalides de condition se produit, vous pouvez éviter la poursuite du traitement et de sortir plus tôt. par exemple,
Je préfère avoir
if (process_first() && process_second() && process_third()) { // do whatever, all success }
et ont la liberté d'utiliser les arguments de la fonction? Cela donne également la flexibilité pour les autres valeurs de retour sur le succès (comme>= 0
) sans avoir besoin de créer une nouvelle macro.Ce n'est également pas une réelle réponse à la question...
cela ne fonctionne pas bien si vous rencontrez des if-else séquences et mettre une macro dans le si-bloc...
OriginalL'auteur pankaj
La raison
do {} while (0)
est utilisé surif (1) {}
est qu'il n'y a aucun moyen que quelqu'un peut modifier le code avant l'appel de la macrodo {} while (0)
à un autre type de bloc. Par exemple, si vous avez appelé une macro entouré parif (1) {}
comme:Qui est en fait un
else if
. La subtile différenceOriginalL'auteur ericcurtin