Les macros sont des morceaux de texte copiés/collés que le préprocesseur place dans le code authentique ; l'auteur de la macro espère que le remplacement produira un code valide.
Il existe trois bonnes "astuces" pour y parvenir :
Aidez la macro à se comporter comme un véritable code
Le code normal est généralement terminé par un point-virgule. Si l'utilisateur voit un code qui n'en a pas besoin...
doSomething(1) ;
DO_SOMETHING_ELSE(2) // <== Hey? What's this?
doSomethingElseAgain(3) ;
Cela signifie que l'utilisateur s'attend à ce que le compilateur produise une erreur si le point-virgule est absent.
Mais la vraie bonne raison est qu'à un moment donné, l'auteur de la macro aura peut-être besoin de remplacer la macro par une véritable fonction (peut-être inlined). La macro doit donc vraiment se comporter comme tel.
Nous devrions donc avoir une macro nécessitant un point-virgule.
Produire un code valide
Comme le montre la réponse de jfm3, il arrive que la macro contienne plus d'une instruction. Et si la macro est utilisée à l'intérieur d'une instruction if, cela posera problème :
if(bIsOk)
MY_MACRO(42) ;
Cette macro pourrait être développée comme suit :
#define MY_MACRO(x) f(x) ; g(x)
if(bIsOk)
f(42) ; g(42) ; // was MY_MACRO(42) ;
Le site g
sera exécutée quelle que soit la valeur de bIsOk
.
Cela signifie que nous devons ajouter une portée à la macro :
#define MY_MACRO(x) { f(x) ; g(x) ; }
if(bIsOk)
{ f(42) ; g(42) ; } ; // was MY_MACRO(42) ;
Produire un code valide 2
Si la macro est quelque chose comme :
#define MY_MACRO(x) int i = x + 1 ; f(i) ;
Nous pourrions avoir un autre problème dans le code suivant :
void doSomething()
{
int i = 25 ;
MY_MACRO(32) ;
}
Parce qu'il s'étendrait comme :
void doSomething()
{
int i = 25 ;
int i = 32 + 1 ; f(i) ; ; // was MY_MACRO(32) ;
}
Ce code ne sera pas compilé, bien sûr. Donc, encore une fois, la solution est d'utiliser un scope :
#define MY_MACRO(x) { int i = x + 1 ; f(i) ; }
void doSomething()
{
int i = 25 ;
{ int i = 32 + 1 ; f(i) ; } ; // was MY_MACRO(32) ;
}
Le code se comporte à nouveau correctement.
Combinaison des effets point-virgule + portée ?
Il existe un idiome C/C++ qui produit cet effet : La boucle do/while :
do
{
// code
}
while(false) ;
Le do/while peut créer une portée, encapsulant ainsi le code de la macro, et nécessite un point-virgule à la fin, s'étendant ainsi au 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 :
#define MY_MACRO(x) \
do \
{ \
const int i = x + 1 ; \
f(i) ; g(i) ; \
} \
while(false)
void doSomething(bool bIsOk)
{
int i = 25 ;
if(bIsOk)
MY_MACRO(42) ;
// Etc.
}
se développera correctement comme
void doSomething(bool bIsOk)
{
int i = 25 ;
if(bIsOk)
do
{
const int i = 42 + 1 ; // was MY_MACRO(42) ;
f(i) ; g(i) ;
}
while(false) ;
// Etc.
}
et est ensuite compilé et optimisé en tant que
void doSomething(bool bIsOk)
{
int i = 25 ;
if(bIsOk)
{
f(43) ; g(43) ;
}
// Etc.
}
3 votes
Pour l'exemple avec le else, j'ajouterais une expression de
void
type à la fin... comme ((void)0) .3 votes
Rappelons que le
do while
n'est pas compatible avec les instructions de retour, de sorte que l'optionif (1) { ... } else ((void)0)
a des usages plus compatibles avec le C standard. Et dans le C GNU, vous préférerez la construction décrite dans ma réponse.