J'ai toujours vu des exemples et des cas où l'utilisation d'une macro est meilleure que l'utilisation d'une fonction.
Quelqu'un pourrait-il m'expliquer avec un exemple l'inconvénient d'une macro par rapport à une fonction?
J'ai toujours vu des exemples et des cas où l'utilisation d'une macro est meilleure que l'utilisation d'une fonction.
Quelqu'un pourrait-il m'expliquer avec un exemple l'inconvénient d'une macro par rapport à une fonction?
Réponses:
Les macros sont sujettes aux erreurs car elles reposent sur la substitution textuelle et n'effectuent pas de vérification de type. Par exemple, cette macro:
#define square(a) a * a
fonctionne bien lorsqu'il est utilisé avec un entier:
square(5) --> 5 * 5 --> 25
mais fait des choses très étranges lorsqu'il est utilisé avec des expressions:
square(1 + 2) --> 1 + 2 * 1 + 2 --> 1 + 2 + 2 --> 5
square(x++) --> x++ * x++ --> increments x twice
Mettre des parenthèses autour des arguments aide mais n'élimine pas complètement ces problèmes.
Lorsque les macros contiennent plusieurs instructions, vous pouvez avoir des problèmes avec les constructions de flux de contrôle:
#define swap(x, y) t = x; x = y; y = t;
if (x < y) swap(x, y); -->
if (x < y) t = x; x = y; y = t; --> if (x < y) { t = x; } x = y; y = t;
La stratégie habituelle pour résoudre ce problème est de placer les instructions dans une boucle "do {...} while (0)".
Si vous avez deux structures qui contiennent un champ avec le même nom mais une sémantique différente, la même macro peut fonctionner sur les deux, avec des résultats étranges:
struct shirt
{
int numButtons;
};
struct webpage
{
int numButtons;
};
#define num_button_holes(shirt) ((shirt).numButtons * 4)
struct webpage page;
page.numButtons = 2;
num_button_holes(page) -> 8
Enfin, les macros peuvent être difficiles à déboguer, produisant des erreurs de syntaxe étranges ou des erreurs d'exécution que vous devez développer pour comprendre (par exemple avec gcc -E), car les débogueurs ne peuvent pas parcourir les macros, comme dans cet exemple:
#define print(x, y) printf(x y) /* accidentally forgot comma */
print("foo %s", "bar") /* prints "foo %sbar" */
Les fonctions et les constantes en ligne permettent d'éviter beaucoup de ces problèmes avec les macros, mais ne sont pas toujours applicables. Lorsque des macros sont délibérément utilisées pour spécifier un comportement polymorphe, le polymorphisme involontaire peut être difficile à éviter. C ++ a un certain nombre de fonctionnalités telles que des modèles pour aider à créer des constructions polymorphes complexes d'une manière sécurisée sans l'utilisation de macros; voir le langage de programmation C ++ de Stroustrup pour plus de détails.
x++*x++
ne peut donc pas dire que l'expression s'incrémente x
deux fois; il invoque en fait un comportement indéfini , ce qui signifie que le compilateur est libre de faire tout ce qu'il veut - il peut s'incrémenter x
deux fois, une fois ou pas du tout; il pourrait avorter avec une erreur ou même faire voler des démons hors de votre nez .
Caractéristiques macro :
Caractéristiques de la fonction :
Les effets secondaires sont importants. Voici un cas typique:
#define min(a, b) (a < b ? a : b)
min(x++, y)
est étendu à:
(x++ < y ? x++ : y)
x
est incrémenté deux fois dans la même instruction. (et comportement indéfini)
L'écriture de macros multilignes est également une douleur:
#define foo(a,b,c) \
a += 10; \
b += 10; \
c += 10;
Ils nécessitent un \
à la fin de chaque ligne.
Les macros ne peuvent rien "renvoyer" à moins que vous n'en fassiez une seule expression:
int foo(int *a, int *b){
side_effect0();
side_effect1();
return a[0] + b[0];
}
Vous ne pouvez pas faire cela dans une macro à moins que vous n'utilisiez l'instruction d'expression de GCC. (EDIT: Vous pouvez cependant utiliser un opérateur virgule ... j'ai oublié cela ... Mais cela pourrait encore être moins lisible.)
Ordre des opérations: (avec l'aimable autorisation de @ouah)
#define min(a,b) (a < b ? a : b)
min(x & 0xFF, 42)
est étendu à:
(x & 0xFF < 42 ? x & 0xFF : 42)
Mais &
a une priorité inférieure à <
. Donc 0xFF < 42
est évalué en premier.
min(a & 0xFF, 42)
#define SQUARE(x) ((x)*(x))
int main() {
int x = 2;
int y = SQUARE(x++); // Undefined behavior even though it doesn't look
// like it here
return 0;
}
tandis que:
int square(int x) {
return x * x;
}
int main() {
int x = 2;
int y = square(x++); // fine
return 0;
}
struct foo {
int bar;
};
#define GET_BAR(f) ((f)->bar)
int main() {
struct foo f;
int a = GET_BAR(&f); // fine
int b = GET_BAR(&a); // error, but the message won't make much sense unless you
// know what the macro does
return 0;
}
Par rapport à:
struct foo {
int bar;
};
int get_bar(struct foo *f) {
return f->bar;
}
int main() {
struct foo f;
int a = get_bar(&f); // fine
int b = get_bar(&a); // error, but compiler complains about passing int* where
// struct foo* should be given
return 0;
}
En cas de doute, utilisez des fonctions (ou des fonctions en ligne).
Cependant, les réponses ici expliquent principalement les problèmes avec les macros, au lieu d'avoir une idée simple que les macros sont mauvaises parce que des accidents stupides sont possibles.
Vous pouvez être conscient des pièges et apprendre à les éviter. Utilisez ensuite les macros uniquement lorsqu'il y a une bonne raison de le faire.
Il existe certains cas exceptionnels où l'utilisation de macros présente des avantages, notamment:
va_args
. __FILE__
, __LINE__
, __func__
). vérifier les conditions pré / post, assert
en cas d'échec ou même d'assertions statiques pour que le code ne se compile pas en cas d'utilisation incorrecte (surtout utile pour les versions de débogage).struct
membres sont présents avant la conversion func(FOO, "FOO");
, vous pouvez définir une macro qui développe la chaîne pour vousfunc_wrapper(FOO);
inline
fonctions peuvent être une option) .Certes, certains d'entre eux reposent sur des extensions de compilateur qui ne sont pas standard C. Cela signifie que vous pouvez vous retrouver avec moins de code portable, ou en avoir besoin ifdef
, donc ils ne sont exploités que lorsque le compilateur prend en charge.
Noter cela car c'est l'une des causes les plus courantes d'erreurs dans les macros (en passant x++
par exemple, où une macro peut s'incrémenter plusieurs fois) .
il est possible d'écrire des macros qui évitent les effets secondaires avec plusieurs instanciations d'arguments.
Si vous aimez avoir une square
macro qui fonctionne avec différents types et qui prend en charge C11, vous pouvez le faire ...
inline float _square_fl(float a) { return a * a; }
inline double _square_dbl(float a) { return a * a; }
inline int _square_i(int a) { return a * a; }
inline unsigned int _square_ui(unsigned int a) { return a * a; }
inline short _square_s(short a) { return a * a; }
inline unsigned short _square_us(unsigned short a) { return a * a; }
/* ... long, char ... etc */
#define square(a) \
_Generic((a), \
float: _square_fl(a), \
double: _square_dbl(a), \
int: _square_i(a), \
unsigned int: _square_ui(a), \
short: _square_s(a), \
unsigned short: _square_us(a))
Il s'agit d'une extension de compilateur supportée par GCC, Clang, EKOPath et Intel C ++ (mais pas MSVC) ;
#define square(a_) __extension__ ({ \
typeof(a_) a = (a_); \
(a * a); })
Donc, l'inconvénient des macros est que vous devez savoir comment les utiliser pour commencer et qu'elles ne sont pas prises en charge aussi largement.
Un avantage est que, dans ce cas, vous pouvez utiliser la même square
fonction pour de nombreux types différents.
Aucune vérification de type des paramètres et du code n'est répétée, ce qui peut entraîner un gonflement du code. La syntaxe de macro peut également conduire à n'importe quel nombre de cas de bord étranges où les points-virgules ou l'ordre de priorité peuvent gêner. Voici un lien qui démontre un mal macro
un inconvénient des macros est que les débogueurs lisent le code source, qui n'a pas de macros développées, donc exécuter un débogueur dans une macro n'est pas nécessairement utile. Inutile de dire que vous ne pouvez pas définir un point d'arrêt dans une macro comme vous le pouvez avec des fonctions.
Les fonctions vérifient le type. Cela vous donne une couche supplémentaire de sécurité.
Ajout à cette réponse…
Les macros sont remplacées directement dans le programme par le préprocesseur (car ce sont essentiellement des directives de préprocesseur). Ils utilisent donc inévitablement plus d'espace mémoire qu'une fonction respective. D'autre part, une fonction nécessite plus de temps pour être appelée et pour renvoyer des résultats, et cette surcharge peut être évitée en utilisant des macros.
Les macros ont également des outils spéciaux qui peuvent aider à la portabilité des programmes sur différentes plates-formes.
Les macros n'ont pas besoin de se voir attribuer un type de données pour leurs arguments contrairement aux fonctions.
Dans l'ensemble, ils sont un outil utile dans la programmation. Et les macro-instructions et les fonctions peuvent être utilisées selon les circonstances.
Je n'ai pas remarqué, dans les réponses ci-dessus, un avantage des fonctions par rapport aux macros que je pense être très important:
Les fonctions peuvent être passées en arguments, les macros ne le peuvent pas.
Exemple concret: vous souhaitez écrire une version alternative de la fonction standard 'strpbrk' qui acceptera, plutôt qu'une liste explicite de caractères à rechercher dans une autre chaîne, une fonction (pointeur vers une) qui renverra 0 jusqu'à ce qu'un caractère soit trouvé qui passe un certain test (défini par l'utilisateur). Une des raisons pour lesquelles vous voudrez peut-être faire cela est de pouvoir exploiter d'autres fonctions de bibliothèque standard: au lieu de fournir une chaîne explicite pleine de ponctuation, vous pouvez passer 'ispunct' de ctype.h à la place, etc. Si 'ispunct' a été implémenté uniquement comme une macro, cela ne fonctionnerait pas.
Il existe de nombreux autres exemples. Par exemple, si votre comparaison est effectuée par macro plutôt que par fonction, vous ne pouvez pas la passer au 'qsort' de stdlib.h.
Une situation analogue en Python est «imprimer» dans la version 2 par rapport à la version 3 (instruction non passable vs fonction passable).
Si vous passez la fonction en tant qu'argument à la macro, elle sera évaluée à chaque fois. Par exemple, si vous appelez l'une des macros les plus populaires:
#define MIN(a,b) ((a)<(b) ? (a) : (b))
comme ça
int min = MIN(functionThatTakeLongTime(1),functionThatTakeLongTime(2));
functionThatTakeLongTime sera évalué 5 fois, ce qui peut réduire considérablement les performances