Dans de nombreuses macros C / C ++, je vois le code de la macro enveloppé dans ce qui semble être une do while
boucle vide de sens . Voici des exemples.
#define FOO(X) do { f(X); g(X); } while (0)
#define FOO(X) if (1) { f(X); g(X); } else
Je ne vois pas ce que ça do while
fait. Pourquoi ne pas simplement écrire cela sans cela?
#define FOO(X) f(X); g(X)
c++
c
c-preprocessor
c++-faq
jfm3
la source
la source
void
type à la fin ... comme ((void) 0) .do while
construction n'est pas compatible avec les instructions de retour, donc laif (1) { ... } else ((void)0)
construction a des usages plus compatibles dans la norme C. Et dans GNU C, vous préférerez la construction décrite dans ma réponse.Réponses:
Le
do ... while
etif ... else
sont là pour faire en sorte qu'un point-virgule après votre macro signifie toujours la même chose. Disons que vous aviez quelque chose comme votre deuxième macro.Maintenant, si vous deviez utiliser
BAR(X);
dans uneif ... else
instruction, où les corps de l'instruction if n'étaient pas placés entre crochets, vous obtiendriez une mauvaise surprise.Le code ci-dessus se développerait en
ce qui est syntaxiquement incorrect, car le reste n'est plus associé à l'if. Cela n'aide pas à encapsuler les choses entre accolades dans la macro, car un point-virgule après les accolades est syntaxiquement incorrect.
Il existe deux façons de résoudre le problème. La première consiste à utiliser une virgule pour séquencer les instructions au sein de la macro sans la priver de sa capacité à agir comme une expression.
La version ci-dessus de bar
BAR
développe le code ci-dessus dans ce qui suit, ce qui est syntaxiquement correct.Cela ne fonctionne pas si, au lieu de cela,
f(X)
vous avez un corps de code plus compliqué qui doit aller dans son propre bloc, par exemple pour déclarer des variables locales. Dans le cas le plus général, la solution consiste à utiliser quelque chose commedo ... while
pour que la macro soit une instruction unique qui prend un point-virgule sans confusion.Vous n'avez pas besoin de l'utiliser
do ... while
, vous pouvez également préparer quelque chose avecif ... else
, bien que quand il seif ... else
développe à l'intérieur d'un,if ... else
cela mène à un " balancement d'autre ", ce qui pourrait rendre un problème de balancement d'autre encore plus difficile à trouver, comme dans le code suivant .Le point est d'utiliser le point-virgule dans des contextes où un point-virgule pendant est erroné. Bien sûr, on pourrait (et devrait probablement) faire valoir à ce stade qu’il serait préférable de déclarer
BAR
comme une fonction réelle, et non comme une macro.En résumé, le
do ... while
est là pour contourner les lacunes du préprocesseur C. Lorsque ces guides de style C vous disent de mettre à pied le préprocesseur C, c'est le genre de chose qui les inquiète.la source
#define BAR(X) (f(X), g(X))
sinon la priorité de l'opérateur peut perturber la sémantique.if
instructions, etc., dans notre code utilisent des accolades, alors encapsuler des macros comme celle-ci est un moyen simple d'éviter les problèmes.if(1) {...} else void(0)
formulaire est plus sûr quedo {...} while(0)
pour les macros dont les paramètres sont du code inclus dans l'expansion de macro, car il ne modifie pas le comportement des mots-clés break ou continue. Par exemple:for (int i = 0; i < max; ++i) { MYMACRO( SomeFunc(i)==true, {break;} ) }
provoque un comportement inattendu lorsqueMYMACRO
est défini comme#define MYMACRO(X, CODE) do { if (X) { cout << #X << endl; {CODE}; } } while (0)
parce que la rupture affecte la boucle while de la macro plutôt que la boucle for sur le site d'appel de macro.void(0)
était une faute de frappe, je veux dire(void)0
. Et je crois que cela ne résout le problème « ballants autre »: avis il n'y a pas après la virgule(void)0
. Un autre pendant dans ce cas (par exempleif (cond) if (1) foo() else (void)0 else { /* dangling else body */ }
) déclenche une erreur de compilation. Voici un exemple en direct le démontrantLes macros sont des morceaux de texte copiés / collés que le pré-processeur mettra dans le code authentique; l'auteur de la macro espère que le remplacement produira un code valide.
Il y a trois bons "conseils" pour y parvenir:
Aidez la macro à se comporter comme un code authentique
Le code normal se termine généralement par un point-virgule. Si l'utilisateur affiche le code qui n'en a pas besoin ...
Cela signifie que l'utilisateur s'attend à ce que le compilateur produise une erreur si le point-virgule est absent.
Mais la vraie vraie bonne raison est qu'à un moment donné, l'auteur de la macro devra peut-être remplacer la macro par une véritable fonction (peut-être en ligne). Donc, la macro devrait vraiment se comporter comme telle.
Nous devrions donc avoir une macro nécessitant un point-virgule.
Produire un code valide
Comme indiqué dans la réponse de jfm3, parfois la macro contient plus d'une instruction. Et si la macro est utilisée dans une instruction if, ce sera problématique:
Cette macro pourrait être étendue comme suit:
La
g
fonction sera exécutée quelle que soit la valeur debIsOk
.Cela signifie que nous devons avoir à ajouter une portée à 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:
Parce qu'il s'élargirait comme:
Ce code ne se compilera pas, bien sûr. Donc, encore une fois, la solution utilise une portée:
Le code se comporte à nouveau correctement.
Combiner les effets point-virgule + scope?
Il existe un idiome C / C ++ qui produit cet effet: La boucle do / while:
Le do / while peut créer une portée, encapsulant ainsi le code de la macro, et a besoin d'un point-virgule à la fin, se développant ainsi dans le code qui en a besoin.
Le bonus?
Le compilateur C ++ optimisera la boucle do / while, car le fait que sa post-condition soit fausse est connu au moment de la compilation. Cela signifie qu'une macro comme:
se développera correctement
et est ensuite compilé et optimisé comme
la source
void doSomething() { int i = 25 ; { int i = x + 1 ; f(i) ; } ; // was MY_MACRO(32) ; }
n'est pas la bonne extension; l'x
expansion devrait être de 32. Une question plus complexe est celle de l'expansion deMY_MACRO(i+7)
. Et une autre est l'expansion deMY_MACRO(0x07 << 6)
. Il y a beaucoup de bonnes choses, mais il y a des i non pointés et des t non croisés.\_\_LINE\_\_
le rendu est __LINE__. À mon humble avis, il est préférable d'utiliser simplement la mise en forme du code pour le code; par exemple,__LINE__
(qui ne nécessite aucune manipulation particulière). PS Je ne sais pas si c'était vrai en 2012; ils ont depuis apporté quelques améliorations au moteur.inline
fonctions (comme le permet la norme)@ jfm3 - Vous avez une belle réponse à la question. Vous voudrez peut-être également ajouter que l'idiome de macro empêche également le comportement involontaire éventuellement plus dangereux (car il n'y a pas d'erreur) avec de simples instructions «si»:
s'étend à:
qui est syntaxiquement correct donc il n'y a pas d'erreur de compilation, mais a la conséquence probablement non voulue que g () sera toujours appelé.
la source
Les réponses ci-dessus expliquent la signification de ces constructions, mais il existe une différence significative entre les deux qui n'a pas été mentionnée. En fait, il y a une raison de préférer le
do ... while
à laif ... else
construction.Le problème de la
if ... else
construction est qu'elle ne vous oblige pas à mettre le point-virgule. Comme dans ce code:Bien que nous ayons omis le point-virgule (par erreur), le code sera étendu à
et compilera en silence (bien que certains compilateurs puissent émettre un avertissement pour le code inaccessible). Mais la
printf
déclaration ne sera jamais exécutée.do ... while
La construction n'a pas un tel problème, car le seul jeton valide après lewhile(0)
est un point-virgule.la source
FOO(1),x++;
ce qui nous donnera à nouveau un faux positif. Utilisez-ledo ... while
et c'est tout.do ... while (0)
c'est préférable, mais il a un inconvénient: Abreak
oucontinue
contrôlera lado ... while (0)
boucle, pas la boucle contenant l'invocation de macro. L'if
astuce a donc toujours de la valeur.break
ou uncontinue
qui serait vu comme dans votredo {...} while(0)
pseudo-boucle de macros . Même dans le paramètre macro, cela ferait une erreur de syntaxe.do { ... } while(0)
au lieu deif whatever
construire, est sa nature idiomatique. Lado {...} while(0)
construction est répandue, bien connue et beaucoup utilisée par de nombreux programmeurs. Sa justification et sa documentation sont facilement connues. Ce n'est pas le cas pour laif
construction. Il faut donc moins d'efforts pour grogner lors de la révision de code.#define CHECK(call, onerr) if (0 != (call)) { onerr } else (void)0
. Il pourrait être utilisé commeCHECK(system("foo"), break;);
, où lebreak;
est destiné à faire référence à la boucle entourant l'CHECK()
invocation.Bien qu'il soit prévu que les compilateurs optimisent les
do { ... } while(false);
boucles, il existe une autre solution qui ne nécessiterait pas cette construction. La solution est d'utiliser l'opérateur virgule:ou encore plus exotiquement:
Bien que cela fonctionne bien avec des instructions distinctes, cela ne fonctionnera pas avec les cas où les variables sont construites et utilisées dans le cadre de
#define
:Avec cela, on serait obligé d'utiliser la construction do / while.
la source
Peterson's Algorithm
.La bibliothèque de préprocesseurs P99 de Jens Gustedt (oui, le fait qu'une telle chose existe m'a aussi fait réfléchir !) Améliore la
if(1) { ... } else
construction de manière petite mais significative en définissant ce qui suit:La raison en est que, contrairement à la
do { ... } while(0)
construction,break
etcontinue
fonctionne toujours à l'intérieur du bloc donné, mais((void)0)
crée une erreur de syntaxe si le point-virgule est omis après l'appel de la macro, ce qui autrement ignorerait le bloc suivant. (Il n'y a pas vraiment de problème de "balançoire d'autre" ici, car leelse
lien au plus procheif
, qui est celui de la macro.)Si vous êtes intéressé par le genre de choses qui peuvent être faites plus ou moins en toute sécurité avec le préprocesseur C, consultez cette bibliothèque.
la source
break
(oucontinue
) à l'intérieur d'une macro pour contrôler une boucle qui a commencé / s'est terminée à l'extérieur, c'est juste un mauvais style et cache des points de sortie potentiels.else ((void)0)
est que quelqu'un écriveYOUR_MACRO(), f();
et qu'il soit syntaxiquement valide, mais n'appelle jamaisf()
. Avecdo
while
c'est une erreur de syntaxe.else do; while (0)
?Pour certaines raisons, je ne peux pas commenter la première réponse ...
Certains d'entre vous ont montré des macros avec des variables locales, mais personne n'a mentionné que vous ne pouvez pas utiliser n'importe quel nom dans une macro! Il mordra 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, vous avez utilisé le nom variable probablement le plus couramment utilisé i .
Par exemple, lorsque la macro suivante
est utilisé dans la fonction suivante
la macro n'utilisera pas la variable i prévue, 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.
Ainsi, n'utilisez jamais de noms de variables communs dans une macro!
la source
int __i;
.mylib_internal___i
ou similaires.Explication
do {} while (0)
etif (1) {} else
doivent s'assurer que la macro est étendue à une seule instruction. Autrement:s'étendrait à:
Et
g(X)
serait exécuté en dehors de laif
déclaration de contrôle. Ceci est évité lors de l'utilisation dedo {} while (0)
etif (1) {} else
.Meilleure alternative
Avec une GNU expression de déclaration ( et non une partie de C standard), 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 (notez que ce
do {} while (0)
n'est pas le cas), comme dans:la source
Je ne pense pas que cela ait été mentionné, alors considérez ceci
serait traduit en
remarquez comment
i++
est évalué deux fois par la macro. Cela peut conduire à des erreurs intéressantes.la source
do { int macroname_i = (i); f(macroname_i); g(macroname_i); } while (/* CONSTCOND */ 0)