Je vois souvent des cas où l'utilisation d'une macro est préférable à l'utilisation d'une fonction.
Quelqu'un pourrait-il m'expliquer avec un exemple l'inconvénient d'une macro par rapport à une fonction ?
Je vois souvent des cas où l'utilisation d'une macro est préférable à l'utilisation d'une fonction.
Quelqu'un pourrait-il m'expliquer avec un exemple l'inconvénient d'une macro par rapport à une fonction ?
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 nombre 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 consiste à placer les instructions à l'intérieur d'une lignée "do { ... } while (0)".
Si vous avez deux structures qui contiennent un champ portant le même nom mais ayant 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 bizarres 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 passer par 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 constantes en ligne permettent d'éviter bon nombre de ces problèmes avec les macros, mais ne sont pas toujours applicables. Lorsque les macros sont délibérément utilisées pour spécifier un comportement polymorphe, le polymorphisme involontaire peut être difficile à éviter. Le C++ dispose d'un certain nombre de fonctionnalités, telles que les modèles, qui permettent de créer des constructions polymorphes complexes de manière sûre, sans avoir recours aux macros. Le langage de programmation C++ pour les détails.
Le C++ est une extension du C qui ajoute (entre autres choses) des fonctionnalités destinées à remédier à cette limitation spécifique du C. Je ne suis pas un fan du C++, mais je pense que c'est le sujet ici.
Les effets secondaires en sont un gros. 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 déclaration. (et un comportement indéfini)
L'écriture de macros de plusieurs lignes est également un problème :
#define foo(a,b,c) \
a += 10; \
b += 10; \
c += 10;
Ils nécessitent un \
à la fin de chaque ligne.
Les macros ne peuvent pas "renvoyer" quoi que ce soit, sauf si vous en faites une expression unique :
int foo(int *a, int *b){
side_effect0();
side_effect1();
return a[0] + b[0];
}
On ne peut pas le faire dans une macro, à moins d'utiliser la méthode GCC expressions de déclaration . (EDIT : Vous pouvez utiliser un opérateur de virgule cependant... oublié que... 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 étendu à :
(x & 0xFF < 42 ? x & 0xFF : 42)
Mais &
a une priorité inférieure à celle de <
. Donc 0xFF < 42
est évalué en premier.
Et le fait de ne pas mettre de parenthèses avec les arguments de la macro dans la définition de la macro peut entraîner des problèmes de précédence : par ex, min(a & 0xFF, 42)
En cas de doute, utilisez des fonctions (ou des fonctions en ligne).
Cependant, les réponses données ici expliquent surtout les problèmes liés aux macros, au lieu de dire simplement que les macros sont mauvaises parce que des accidents stupides sont possibles.
Vous pouvez être conscient des pièges et apprendre à les éviter. N'utilisez ensuite les macros que lorsque vous avez une bonne raison de le faire.
Il y a certains exceptionnel Il existe des cas 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 des affirmations statiques pour que le code ne compile pas en cas de mauvaise utilisation (surtout utile pour les constructions de débogage).struct
les membres sont présents avant le castingfunc(FOO, "FOO");
vous pouvez définir une macro qui développe la chaîne de caractères à votre place. func_wrapper(FOO);
inline
peut être une option) .Il est vrai que certaines d'entre elles s'appuient sur des extensions du compilateur qui ne font pas partie du langage C standard. ifdef
de sorte qu'ils ne sont utilisés que lorsque le compilateur les prend en charge.
Il faut noter que c'est l'une des causes les plus courantes d'erreurs dans les macros. (en passant dans x++
par exemple, lorsqu'une macro peut s'incrémenter plusieurs fois) .
il est possible d'écrire des macros qui évitent les effets secondaires liés à l'instanciation multiple des arguments.
Si vous aimez avoir square
qui fonctionne avec différents types et qui a un support C11, vous pourriez faire ceci...
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); })
L'inconvénient des macros est donc que vous devez savoir 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 le même square
pour de nombreux types différents.
"...soutenu comme largement..." Je parie que l'expression que vous avez mentionnée n'est pas supportée par cl.exe ? (Compilateur de MS)
#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;
}
alors 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;
}
Il n'y a pas de vérification du type des paramètres et le code est répété, ce qui peut entraîner un gonflement du code. La syntaxe des macros peut également conduire à un certain nombre de cas limites étranges où les points-virgules ou l'ordre de préséance peuvent s'interposer. Voici un lien qui présente quelques macros maléfique
Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.
23 votes
Retournez la question dans sa tête. Dans quelle situation une macro est-elle meilleure ? Utilisez une fonction réelle, sauf si vous pouvez démontrer qu'une macro est meilleure.