2009-10-28 5 views
11

Dans un précédent question ce que je pensais était une bonne réponse a été rejetée pour l'utilisation suggérée des macrosQuand devriez-vous utiliser des macros à la place des fonctions intégrées?

#define radian2degree(a) (a * 57.295779513082) 
#define degree2radian(a) (a * 0.017453292519) 

au lieu des fonctions en ligne. Veuillez excuser la question de débutant, mais qu'est-ce qui est si mauvais au sujet des macros dans ce cas?

+9

Dans ce cas, degree2radian (45 + a/2.0) ne fait pas ce que vous pensiez. Toujours entre parenthèses chaque argument dans l'expansion - bien, presque toujours; Les principales exceptions sont lorsque vous faites des choses comme le collage de jetons ou le chaînage. –

+3

ce que Jonathan dit - une chose pire que de voir une définition de macro qui est une douleur dans le cul à lire à cause de toutes les foutues parens, est de tomber sur une définition macro qui n'a pas toutes ces foutues parens. –

+3

Il existe également la convention ALLCAPS pour les macros, qui les place au moins dans leur propre pseudo-espace de noms. –

Répondre

6

Il y a quelques trucs strictement diaboliques à propos des macros.

Ils sont un traitement de texte et ne sont pas définis. Si vous #define foo 1, toute utilisation ultérieure de foo en tant qu'identificateur échouera. Cela peut conduire à des erreurs de compilation bizarres et à des bugs d'exécution difficiles à trouver.

Ils ne prennent pas d'arguments dans le sens normal. Vous pouvez écrire une fonction qui prendra deux valeurs int et renvoyer le maximum, car les arguments seront évalués une fois et les valeurs utilisées par la suite. Vous ne pouvez pas écrire de macro pour faire cela, car il va évaluer au moins un argument deux fois, et échouer avec quelque chose comme max(x++, --y).

Il y a aussi des pièges courants. Il est difficile d'obtenir plusieurs énoncés en eux, et ils nécessitent beaucoup de parenthèses éventuellement superflues.

Dans votre cas, vous avez besoin entre parenthèses:

#define radian2degree(a) (a * 57.295779513082) 

a besoin d'être

#define radian2degree(a) ((a) * 57.295779513082) 

et vous intensifions encore personne qui écrit une fonction radian2degree dans une certaine marge intérieure, confiant que ce définition fonctionnera dans sa propre portée.

+1

Je trouve toujours que les expressions de "feature 'x' est mal ne jamais l'utiliser" semblent impliquer que les programmeurs sont immensément stupides. Recommandez que les macros soient nommées d'une manière qui s'identifie comme des macros lorsqu'elles sont utilisées: RADIAN2DEGREEmac ou similaire. –

+5

"La caractéristique X est diabolique" ne signifie pas "ne pas utiliser la caractéristique X", et je n'ai pas vraiment dit que les macros étaient mauvaises en elles-mêmes. La convention des majuscules est précieuse, mais ne résout pas tous les problèmes. Pareil avec la parenthèse stricte et les autres astuces. Je recommande d'éviter les macros fonctionnelles, si possible, car il y a généralement de meilleures façons de faire ce que vous essayez de faire. –

+1

@David Bonne réponse mais elle n'a pas répondu à la question: "Quand faut-il utiliser des macros au lieu de fonctions en ligne?" Il semble que vous avez répondu le contraire: pourquoi vous ne devriez pas utiliser de macros. Mais quand est-il "préférable" d'utiliser une macro au lieu d'une fonction? – styfle

1

si possible, utilisez toujours la fonction inline. Ce sont des typesafe et ne peuvent pas être facilement redéfinis.

Les définitions peuvent être redéfinies indéfinies et il n'y a pas de vérification de type.

+1

L'inconvénient de C (par opposition à C++) est que tous les compilateurs ne les supportent pas nécessairement - ou par défaut à un mode où ils sont supportés. Ils ont été ajoutés en C99, mais certains compilateurs (MSVC, par exemple) ne sont pas conformes au C99. Je ne sais pas si MSVC prend en charge cette partie de la norme - cela peut être dû au fait que le support C++ est également présent. Cependant, il n'a pas d'autres fonctionnalités C99 qui seraient très agréables à avoir. –

+2

Un compilateur intelligent ignorera la directive 'inline' et en ligne ou non comme l'indique l'appel individuel. –

+0

@David +1, inline est une pure recommandation. – LB40

7

Pour cette macro spécifique, si je l'utilise comme suit:

int x=1; 
x = radian2degree(x); 
float y=1; 
y = radian2degree(y); 

il n'y aurait pas de vérification de type, et x, y contiendra des valeurs différentes.

En outre, le code suivant

float x=1, y=2; 
float z = radian2degree(x+y); 

ne fera pas ce que vous pensez, car il se traduira par

float z = x+y*0.017453292519; 

au lieu de

float z = (x+y)+0.017453292519; 

qui est le résultat attendu.

Ce ne sont là que quelques exemples d'erreurs de comportement et de mauvaises utilisations des macros.

Modifier

vous pouvez voir d'autres discussions au sujet de cette here

+3

Bonne réponse. Il convient également de mentionner la pollution de l'espace de noms (par exemple, je ne peux plus déclarer une variable locale ou un champ struct nommé radian2degree). –

+0

Je ne comprends pas votre première plainte. x ne sera pas transformé en virgule flottante par la macro, mais par la multiplication. –

+1

mon point était les deux utilisations sont valides et compilera, mais vous ne verrez aucune indication pour cela. Votre macro ne doit pas accepter de nombres flottants, mais elle accepte tout. – Amirshk

0

Les macros sont mal parce que vous pouvez finir par passer plus d'une variable ou un scalaire et cela pourrait résoudre dans un comportement indésirable (définir une macro max pour déterminer max entre a et b mais passer un ++ et b ++ à la macro et voir ce qui se passe).

0

Si votre fonction est de toute façon inline, il n'y a pas de différence de performance entre une fonction et une macro. Cependant, il existe plusieurs différences d'utilisabilité entre une fonction et une macro, qui toutes favorisent l'utilisation d'une fonction. Si vous construisez correctement la macro, il n'y a pas de problème. Mais si vous utilisez une fonction, le compilateur la fera correctement pour vous à chaque fois. Donc, en utilisant une fonction, il est plus difficile d'écrire du mauvais code.

2

Les macros sont souvent utilisées abusivement et on peut facilement faire des erreurs en les utilisant comme le montre votre exemple. Prenez l'expression radian2degree (1 + 1):

  • avec la macro, il sera étendu à 1 + 1 * = 57,29 ... 58,29 ...
  • avec une fonction, il sera ce que vous voulez qu'il être, à savoir (1 + 1) * 57.29 ... = ...

Plus généralement, les macros sont mal parce qu'ils ressembler à des fonctions afin qu'ils vous tromper en les utilisant comme des fonctions mais ils ont des règles subtiles de leur propre. Dans ce cas, la bonne façon serait d'écrire ce serait (notez le autour parenthèse):

#define radian2degree(a) ((a) * 57.295779513082) 

Mais vous devez en tenir à des fonctions inline. Voir ces liens de C++ FAQ Lite pour plus d'exemples de macros mal et leurs subtilités:

+1

+1 mais a) les macros ne sont pas "mal en général". Ils peuvent être maltraités, mais ils ne sont pas la racine de tous les maux, pas plus que les «goto». b) Pourquoi créer un lien vers la FAQ C++ pour une question C? Il n'y a pas de balise C++ et pas d'informations spécifiques à C++ dans la question. –

+0

Vous avez raison à propos du "mal en général", c'était trop dur et général. Je l'ai édité. En ce qui concerne b), pour autant que je vois toutes les informations dans la FAQ C++, les liens que j'ai donnés s'appliquent aussi à C et je pense que les problèmes/subtilités des macros y sont clairement expliqués, donc je les vois comme des liens précieux pour cette question . Alors pourquoi ne pas créer un lien vers la FAQ C++? –

1

le préprocesseur du compilateur est une chose finnicky , et donc un candidat terrible pour des astuces intelligentes. Comme d'autres l'ont souligné, il est facile pour le compilateur de mal comprendre votre intention avec la macro, et il est facile pour vous de mal comprendre ce que fera la macro, mais surtout, vous ne pouvez pas entrer dans les macros du débogueur!

8

La plupart des autres réponses expliquent pourquoi les macros sont mauvaises, y compris comment votre exemple a un défaut commun d'utilisation des macros. Voici la prise de Stroustrup: http://www.research.att.com/~bs/bs_faq2.html#macro

Mais votre question était de savoir quelles macros sont toujours bonnes.Il y a des choses où les macros sont mieux que les fonctions inline, et c'est là que vous faites des choses qui ne peuvent simplement pas être fait avec fonctions en ligne, tels que:

  • coller
  • jeton portant les numéros de ligne ou par exemple (comme pour la création de messages d'erreur dans assert())
  • traitant des choses qui ne sont pas des expressions (par exemple le nombre de mises en œuvre de offsetof() utilisation en utilisant un nom de type pour créer une opération de coulée)
  • la macro pour obtenir un compte de éléments de tableau (ne peut pas le faire avec une fonction, car le nom du tableau se décompose en un pointeur e asily)
  • créant des choses comme la fonction « type polymorphes » dans C où les modèles ne sont pas disponibles

Mais avec une langue qui a des fonctions inline, les utilisations les plus courantes de macros ne devraient pas être nécessaires. Je suis même réticent à utiliser des macros quand j'ai affaire à un compilateur C qui ne supporte pas les fonctions en ligne. Et j'essaie de ne pas les utiliser pour créer des fonctions agnostiques de type si possible (créer plusieurs fonctions avec un indicateur de type comme une partie du nom à la place).

Je suis également passé à l'utilisation d'enums pour les constantes numériques nommées au lieu de #define.

+0

Un autre cas utile pour les macros est l'initialisation d'objets dans le segment de données: int global = radian2degree (2 * PI); – calandoa

+0

Yup - Je traite rarement les nombres à virgule flottante moi-même, donc j'utilise généralement des énumérations maintenant à la place des macros pour les constantes numériques nommées. Mais il est clair que cela ne peut pas être fait pour des flottants comme "PI", donc une macro ce serait. –

+0

Sur les compilateurs avec des extensions gcc, les macros, contrairement aux fonctions en ligne, peuvent tester si leurs arguments peuvent être évalués comme des constantes. Si l'on souhaite avoir une "fonction" comme 'reverse8bits (x)' qui calcule une valeur d'octet inversée par bit, il vaut mieux utiliser '(((x & 128) >> 7) | ((x & 64) >> 5) ...) 'quand' x' est une constante (auquel cas l'expression à l'aspect désagréable donne une constante) mais appelle une routine de bibliothèque quand 'x' est une variable. Je souhaite que les fonctions inline permises 'C' soient surchargées selon que les arguments sont des constantes de compilation, puisque cela serait plus propre, et ... – supercat

Questions connexes