Les macros sont comme n'importe quel autre outil - un marteau utilisé dans un meurtre n'est pas mauvais parce que c'est un marteau. C'est mauvais dans la manière dont la personne l'utilise de cette manière. Si vous voulez enfoncer des clous, un marteau est un outil parfait.
Il y a quelques aspects des macros qui les rendent "mauvaises" (je les développerai plus tard et suggérerai des alternatives):
- Vous ne pouvez pas déboguer les macros.
- L'expansion des macros peut entraîner d'étranges effets secondaires.
- Les macros n'ont pas d '"espace de noms", donc si vous avez une macro qui entre en conflit avec un nom utilisé ailleurs, vous obtenez des remplacements de macro là où vous ne le vouliez pas, ce qui conduit généralement à des messages d'erreur étranges.
- Les macros peuvent affecter des choses que vous ne réalisez pas.
Alors développons un peu ici:
1) Les macros ne peuvent pas être déboguées.
Lorsque vous avez une macro qui se traduit par un nombre ou une chaîne, le code source aura le nom de la macro et de nombreux débogueurs, vous ne pouvez pas "voir" ce que la macro se traduit. Donc, vous ne savez pas vraiment ce qui se passe.
Remplacement : utilisez enum
ouconst T
Pour les macros «fonctionnelles», comme le débogueur fonctionne au niveau «par ligne source où vous êtes», votre macro agira comme une seule instruction, qu'il s'agisse d'une instruction ou d'une centaine. Rend difficile de comprendre ce qui se passe.
Remplacement : utilisez des fonctions - en ligne si cela doit être "rapide" (mais attention, trop de fonctions en ligne n'est pas une bonne chose)
2) Les extensions de macros peuvent avoir des effets secondaires étranges.
Le célèbre est #define SQUARE(x) ((x) * (x))
et l'utilisation x2 = SQUARE(x++)
. Cela conduit à x2 = (x++) * (x++);
ce qui, même s'il s'agissait de code valide [1], ne serait certainement pas ce que le programmeur voulait. Si c'était une fonction, ce serait bien de faire x ++, et x n'incrémenterait qu'une seule fois.
Un autre exemple est "if else" dans les macros, disons que nous avons ceci:
#define safe_divide(res, x, y) if (y != 0) res = x/y;
puis
if (something) safe_divide(b, a, x);
else printf("Something is not set...");
Cela devient en fait complètement faux ...
Remplacement : de vraies fonctions.
3) Les macros n'ont pas d'espace de noms
Si nous avons une macro:
#define begin() x = 0
et nous avons du code en C ++ qui utilise begin:
std::vector<int> v;
... stuff is loaded into v ...
for (std::vector<int>::iterator it = myvector.begin() ; it != myvector.end(); ++it)
std::cout << ' ' << *it;
Maintenant, quel message d'erreur pensez-vous obtenir, et où recherchez-vous une erreur [en supposant que vous avez complètement oublié - ou même pas au courant - la macro de début qui se trouve dans un fichier d'en-tête écrit par quelqu'un d'autre? [et encore plus amusant si vous incluiez cette macro avant l'inclusion - vous seriez noyé dans des erreurs étranges qui n'ont absolument aucun sens quand vous regardez le code lui-même.
Remplacement : Eh bien, il n'y a pas tant un remplacement qu'une «règle» - n'utilisez que des noms en majuscules pour les macros, et n'utilisez jamais tous les noms en majuscules pour d'autres choses.
4) Les macros ont des effets que vous ne réalisez pas
Prenez cette fonction:
#define begin() x = 0
#define end() x = 17
... a few thousand lines of stuff here ...
void dostuff()
{
int x = 7;
begin();
... more code using x ...
printf("x=%d\n", x);
end();
}
Maintenant, sans regarder la macro, vous penseriez que begin est une fonction, qui ne devrait pas affecter x.
Ce genre de chose, et j'ai vu des exemples beaucoup plus complexes, peut VRAIMENT gâcher votre journée!
Remplacement : n'utilisez pas de macro pour définir x ou transmettez x comme argument.
Il y a des moments où l'utilisation de macros est vraiment bénéfique. Un exemple consiste à envelopper une fonction avec des macros pour transmettre des informations de fichier / ligne:
#define malloc(x) my_debug_malloc(x, __FILE__, __LINE__)
#define free(x) my_debug_free(x, __FILE__, __LINE__)
Maintenant, nous pouvons utiliser my_debug_malloc
comme malloc régulier dans le code, mais il a des arguments supplémentaires, donc quand il s'agit de la fin et que nous analysons les "éléments de mémoire qui n'ont pas été libérés", nous pouvons afficher où l'allocation a été faite afin que le le programmeur peut localiser la fuite.
[1] Il est indéfini de mettre à jour une variable plus d'une fois "dans un point de séquence". Un point de séquence n'est pas exactement la même chose qu'une instruction, mais dans la plupart des cas, c'est ce que nous devrions considérer comme. Cela x++ * x++
entraînera une mise à jour x
deux fois, ce qui n'est pas défini et entraînera probablement des valeurs différentes sur différents systèmes, x
ainsi qu'une valeur de résultat différente .
#pragma
n'est pas macro.