J'ai toujours vu des exemples et des cas où utiliser une macro est préférable à une fonction.
Quelqu'un pourrait-il m'expliquer par un exemple l'inconvénient d'une macro par rapport à une fonction?
Les macros sont sujettes aux erreurs car elles reposent sur une 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 ne supprime pas complètement ces problèmes.
Lorsque les macros contiennent plusieurs instructions, vous pouvez avoir des problèmes avec les constructions de contrôle-flux:
#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 consiste à placer les instructions dans une boucle "do {...} while (0)".
Si vous avez deux structures qui contiennent un champ avec le même nom mais avec 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, il peut être difficile de déboguer les macros, ce qui peut générer des erreurs de syntaxe étranges ou des erreurs d’exécution que vous devez développer pour les 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 problèmes de macros, mais ne sont pas toujours applicables. Lorsque des macros sont délibérément utilisées pour spécifier un comportement polymorphique, il peut être difficile d'éviter un polymorphisme involontaire. Le C++ comporte un certain nombre de fonctionnalités, telles que des modèles, qui permettent de créer des constructions polymorphes complexes de manière sécurisée sans utiliser de macros. voir Stroustrup's langage de programmation C++ pour plus de détails.
Fonctionnalités 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 élargi à:
(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 un problème:
#define foo(a,b,c) \
a += 10; \
b += 10; \
c += 10;
Ils ont besoin d'un \
à la fin de chaque ligne.
Les macros ne peuvent rien "retourner" à moins d'en faire une expression unique:
int foo(int *a, int *b){
side_effect0();
side_effect1();
return a[0] + b[0];
}
Ne peut pas faire cela dans une macro à moins d'utiliser la déclaration d'expression de GCC. (EDIT: Vous pouvez cependant utiliser un opérateur de virgule ... oubliez cela ... Mais cela pourrait quand même être moins lisible.)
Ordre des opérations: (courtoisie de @ouah)
#define min(a,b) (a < b ? a : b)
min(x & 0xFF, 42)
est élargi à:
(x & 0xFF < 42 ? x & 0xFF : 42)
Mais &
a une priorité inférieure à <
. Alors 0xFF < 42
est évalué en premier.
#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 posés par les macros, au lieu d’avoir une idée simple que les macros sont néfastes, car des accidents stupides sont possibles.
Vous pouvez être conscient des pièges et apprendre à les éviter. Ensuite, utilisez les macros uniquement lorsqu'il existe une bonne raison.
Il existe certains exceptionnels cas où l'utilisation de macros présente des avantages, notamment:
va_args
De C.__FILE__
, __LINE__
, __func__
). vérifie les conditions préalables/postérieures, assert
en cas d'erreur, ou même des assertions statiques afin que le code ne se compile pas en cas d'utilisation incorrecte (surtout utile pour les versions de débogage).struct
membres présents avant la distribution.func(FOO, "FOO");
, vous pouvez définir une macro qui développe la chaîne pour vous func_wrapper(FOO);
inline
fonctions peut ê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 un code moins portable, ou devoir les ifdef
en, de sorte qu'ils ne sont exploités que lorsque le compilateur prend en charge .
Notant 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 la macro square
qui fonctionne avec différents types et supporte 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))
C'est 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 les utiliser pour commencer, et qu’elles ne sont pas aussi largement supportées.
Un avantage est que, dans ce cas, vous pouvez utiliser la même fonction square
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 des macros peut également conduire à un nombre incalculable de cas Edge étranges où des points-virgules ou un ordre de priorité peuvent gêner. Voici un lien qui illustre une macro evil
l’un des inconvénients des macros est que les débogueurs lisent le code source, qui n’a pas de macros étendues. Il n’est donc pas utile d’exécuter un débogueur dans une macro. Inutile de dire que vous ne pouvez pas définir de point d'arrêt dans une macro comme vous pouvez le faire avec des fonctions.
Ajout à cette réponse ..
Le préprocesseur remplace directement les macros dans le programme (car ce sont en principe des directives du préprocesseur). Donc, ils utilisent 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 renvoyer les résultats, et cette surcharge peut être évitée en utilisant des macros.
Les macros disposent également d’outils spéciaux pouvant aider à la portabilité des programmes sur différentes plates-formes.
Il n'est pas nécessaire d'attribuer un type de données aux macros pour leurs arguments contrairement aux fonctions.
Globalement, ils constituent un outil utile en programmation. Et les macro-instructions et les fonctions peuvent être utilisées en fonction des circonstances.
Les fonctions font la vérification. Cela vous donne une couche supplémentaire de sécurité.
Je n'ai pas remarqué, dans les réponses ci-dessus, un avantage des fonctions par rapport aux macros que je considère très important:
Les fonctions peuvent être passées en arguments, les macros ne peuvent pas.
Exemple concret: Vous voulez écrire une autre version de la fonction standard 'strpbrk' qui acceptera, plutôt qu'une liste explicite de caractères à rechercher dans une autre chaîne, un (pointeur sur a) fonction qui retournera 0 jusqu'à ce qu'un caractère soit trouvé qui passe un 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 remplie de ponctuation, vous pouvez passer le "ispunct" de ctype.h à la place, etc. Si "ispunct" était implémenté uniquement une macro, ça ne marcherait pas
Il y a beaucoup d'autres exemples. Par exemple, si votre comparaison est effectuée avec une macro plutôt qu'avec une fonction, vous ne pouvez pas la transmettre à 'qsort' de stdlib.h.
Une situation analogue dans Python est 'print' dans la version 2 par rapport à la version 3 (instruction non passable par rapport à une fonction passable).
Si vous passez la fonction comme argument à une macro, celle-ci 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