web-dev-qa-db-fra.com

Macro vs fonction en C

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?

91
Kyrol

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.

102
D Coetzee

Fonctionnalités macro:

  • La macro est prétraité
  • Aucune vérification de type
  • Longueur du code Augmente
  • L'utilisation de la macro peut conduire à effet secondaire
  • La vitesse d'exécution est plus rapide
  • Avant la compilation, le nom de la macro est remplacé par la valeur de la macro
  • Utile lorsque le petit code apparaît plusieurs fois
  • La macro fait pas Vérifier les erreurs de compilation

caractéristiques de la fonction:

  • La fonction est compilé
  • Le type est vérifié
  • La longueur du code reste identique
  • Non Effet secondaire
  • La vitesse d'exécution est plus lente
  • Pendant l'appel de fonction, le transfert de contrôle a lieu
  • Utile lorsque de gros codes apparaissent plusieurs fois
  • Vérifications de fonction Erreurs de compilation
32
zangw

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.

30
Mysticial

Exemple 1:

#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;
}

Exemple 2:

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;
}
13
Flexo

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:

  • Comme indiqué ci-dessous, les fonctions génériques peuvent avoir une macro qui peut être utilisée sur différents types d’arguments d’entrée.
  • Un nombre variable d'arguments peut être mappé sur différentes fonctions au lieu d'utiliser le va_args De C.
    Exemple: https://stackoverflow.com/a/24837037/432509 .
  • Ils peuvent éventuellement inclure des informations locales, telles que des chaînes de débogage:
    (__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).
  • Inspecter les arguments d'entrée. Vous pouvez effectuer des tests sur les arguments d'entrée tels que la vérification de leur type, leur taille, leur vérification struct membres présents avant la distribution.
    (peut être utile pour les types polymorphes).
    Ou vérifiez qu'un tableau remplit certaines conditions de longueur.
    voir: https://stackoverflow.com/a/29926435/432509
  • Bien que les fonctions de frappe soient vérifiées par C, C contraint également les valeurs (ints/floats par exemple). Dans de rares cas, cela peut être problématique. Il est possible d’écrire des macros plus exigeantes qu’une fonction sur leurs arguments d’entrée. voir: https://stackoverflow.com/a/25988779/432509
  • Leur utilisation en tant que wrappers à des fonctions, dans certains cas, vous pouvez éviter de vous répéter, par exemple ... func(FOO, "FOO");, vous pouvez définir une macro qui développe la chaîne pour vous func_wrapper(FOO);
  • Lorsque vous souhaitez manipuler des variables dans la portée locale des appelants, le fait de passer un pointeur à un pointeur fonctionne normalement, mais dans certains cas, il est moins difficile d’utiliser une macro.
    (les assignations à plusieurs variables, pour une opération par pixel, est un exemple où vous préférerez peut-être une macro à une fonction ... bien que cela dépende toujours beaucoup du contexte, puisque 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 .


Éviter l'instanciation de plusieurs arguments

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.

C11 Generic

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))

expressions de déclaration

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.

11
ideasman42

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

11
Michael Dorgan

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.

6
jim mcnamara

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.

6
Nikos

Les fonctions font la vérification. Cela vous donne une couche supplémentaire de sécurité.

5
ncmathsadist

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).

2
Sean Rostami

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

1
Saffer