J'ai essayé de jouer avec la définition de la macro SQR
dans le code suivant:
#define SQR(x) (x*x)
int main()
{
int a, b=3;
a = SQR(b+5); // Ideally should be replaced with (3+5*5+3), though not sure.
printf("%d\n",a);
return 0;
}
Il imprime 23
. Si je change la définition de la macro en SQR(x) ((x)*(x))
alors la sortie est comme prévu, 64
. Je sais qu’un appel à une macro en C remplace l’appel par la définition de la macro, mais je ne comprends toujours pas comment il a calculé 23
.
Les macros du pré-processeur effectuent le remplacement de texte avant la compilation du code afin que SQR(b+5)
se traduise par (b + 5 * b + 5) = (6b + 5) = 6 * 3 + 5 = 23
Les appels de fonction réguliers calculeraient la valeur du paramètre (b + 3) avant de le passer à la fonction, mais comme une macro est un remplacement précompilé, l'ordre algébrique des opérations devient très important.
Car (3+5*3+5 == 23)
.
Tandis que ((3+5)*(3+5)) == 64
.
La meilleure façon de le faire est de ne pas utiliser de macro:
inline int SQR(int x) { return x*x; }
Ou écrivez simplement x*x
.
Considérez le remplacement de macro en utilisant cette macro:
#define SQR(x) (x*x)
Utiliser b+5
Comme argument. Faites le remplacement vous-même. Dans votre code, SQR(b+5)
deviendra: (b+5*b+5)
Ou (3+5*3+5)
. Rappelez-vous maintenant vos règles de priorité de l'opérateur : *
Avant +
. Donc, cela est évalué comme: (3+15+5)
, Ou 23
.
La deuxième version de la macro:
#define SQR(x) ((x) * (x))
Est correct, car vous utilisez les parenthèses pour protéger vos arguments de macro des effets de la priorité de l'opérateur.
Cette page expliquant la préférence de l'opérateur pour C a un joli graphique. Voici la section pertinente du document de référence C11.
La chose à retenir ici est que vous devez prendre l'habitude de toujours protéger les arguments dans vos macros, en utilisant des parens.
La macro se développe pour
a = b+5*b+5;
c'est à dire.
a = b + (5*b) + 5;
Donc 23.
Après le prétraitement, SQR(b+5)
sera étendu à (b+5*b+5)
. Ce n'est évidemment pas correct.
Il existe deux erreurs courantes dans la définition de SQR
:
ne placez pas d'arguments de macro entre parenthèses dans le corps de la macro, donc si ces arguments sont des expressions, les opérateurs avec des précédences différentes dans ces expressions peuvent poser problème. Voici une version qui a résolu ce problème
#define SQR(x) ((x)*(x))
évaluer les arguments de macro plus d'une fois, donc si ces arguments sont des expressions qui ont un effet secondaire, ces effets secondaires peuvent être pris plusieurs fois. Par exemple, considérons le résultat de SQR(++x)
.
En utilisant l'extension GCC typeof , ce problème peut être résolu comme ceci
#define SQR(x) ({ typeof (x) _x = (x); _x * _x; })
Ces deux problèmes pourraient être résolus en remplaçant cette macro par une fonction en ligne
inline int SQR(x) { return x * x; }
Cela nécessite une extension en ligne GCC ou C99, voir 6.40 Une fonction en ligne est aussi rapide qu'une macro .
Une macro n'est qu'une simple substitution de texte. Après le prétraitement, votre code ressemble à:
int main()
{
int a, b=3;
a = b+5*b+5;
printf("%d\n",a);
return 0;
}
La multiplication a une priorité d'opérateur plus élevée que l'addition, elle est donc effectuée avant les deux ajouts lors du calcul de la valeur de a
. L'ajout de parenthèses à votre définition de macro résout le problème en le faisant:
int main()
{
int a, b=3;
a = (b+5)*(b+5);
printf("%d\n",a);
return 0;
}
Les opérations entre parenthèses sont évaluées avant la multiplication, donc les ajouts ont lieu en premier maintenant, et vous obtenez le a = 64
résultat que vous attendez.
Parce que les macros sont juste un remplacement de chaîne et cela se produit avant le processus d'achèvement. Le compilateur n'aura pas la chance de voir la variable Macro et sa valeur. Par exemple: si une macro est définie comme
#define BAD_SQUARE(x) x * x
et appelé comme ça
BAD_SQUARE(2+1)
le compilateur verra cela
2 + 1 * 2 + 1
ce qui entraînera, peut-être, un résultat inattendu de
5
Pour corriger ce comportement, vous devez toujours entourer les macro-variables de parenthèses, telles que
#define GOOD_SQUARE(x) (x) * (x)
lorsque cette macro est appelée, par exemple, comme ceci
GOOD_SQUARE(2+1)
le compilateur verra cela
(2 + 1) * (2 + 1)
qui se traduira par
9
De plus, voici un exemple complet pour illustrer davantage le point
#include <stdio.h>
#define BAD_SQUARE(x) x * x
// In macros alsways srround the variables with parenthesis
#define GOOD_SQUARE(x) (x) * (x)
int main(int argc, char const *argv[])
{
printf("BAD_SQUARE(2) = : %d \n", BAD_SQUARE(2) );
printf("GOOD_SQUARE(2) = : %d \n", GOOD_SQUARE(2) );
printf("BAD_SQUARE(2+1) = : %d ; because the macro will be \
subsituted as 2 + 1 * 2 + 1 \n", BAD_SQUARE(2+1) );
printf("GOOD_SQUARE(2+1) = : %d ; because the macro will be \
subsituted as (2 + 1) * (2 + 1) \n", GOOD_SQUARE(2+1) );
return 0;
}