Dupliquer possible:
À quoi servent les macros C?
Tous les quelques mois, j'ai envie d'aller apprendre un peu de C que mes études de programmation universitaire de merde ne couvraient jamais. Aujourd'hui c'est des macros. Ma compréhension de base des macros est qu’il s’agit d’une simple recherche et remplacement qui se produit sur votre code antérieur à sa compilation. J'ai du mal à comprendre pourquoi vous utiliseriez des macros. La plupart des exemples de base que je regarde sont quelque chose comme
TEST(a,%d);
#define TEST(a,b) printf(" The value of " #a " = " #b " \n", a)
//which expands to
printf(" The value of a = %d \n",a);
(exemple de ici )
De mon point de vue novice, il semble que définir une nouvelle fonction vous donnerait les mêmes résultats. Je peux voir comment, historiquement, les macros seraient utiles pour modifier rapidement beaucoup de sources dans les jours précédant la recherche et le remplacement faciles, mais quelque chose me dit que je manque un point plus important.
Alors, quel genre de choses utiles les macros peuvent-elles vous apporter?
Une des raisons est que, jusqu'à C99, le mot clé inline n'était pas standard dans le langage C. Ainsi, les macros vous ont permis d’intégrer de petites fonctions. À certains égards, ils fonctionnent également comme des modèles, c.-à-d. vous n'avez pas besoin de spécifier des types dans la définition de la macro, par exemple:
#define MAX(x,y) ((x) > (y) ? (x) : (y))
Cette macro est compatible avec les entiers, les doubles, les flottants, etc.
Ce n'est pas exactement rechercher et remplacer, c'est une extension de jeton. Les macros C sont ce que chaque type de macro est dans le monde informatique: un moyen d'écrire quelque chose de court et simple et de le transformer automatiquement en quelque chose de plus long et de plus compliqué.
Une des raisons pour lesquelles les macros sont utilisées est la performance. Ils sont un moyen d’éliminer le temps système d’appel de fonction car ils sont toujours développés en ligne, contrairement au mot-clé "inline" qui est un hint souvent ignoré pour le compilateur et qui n’existait même pas (dans la norme). avant C99. Par exemple, voir la famille FD_ de macros utilisées conjointement aux fd_sets utilisés par select et pselect. Ces fd_sets ne sont en réalité que des bits et les macros FD_ cachent des opérations de retournement de bits. Il serait ennuyeux d'écrire le bit en tournant à chaque fois, et un appel de fonction occasionnerait beaucoup de surcharge pour une opération aussi rapide si elle n'était pas en ligne.
En outre, les macros peuvent faire certaines choses que les fonctions ne peuvent pas. Envisagez de coller des jetons. Comme le préprocesseur s'exécute avant le compilateur, il peut créer de nouveaux identifiants que le compilateur peut utiliser. Cela peut vous donner un moyen simple de créer beaucoup de définitions similaires, par exemple.
#define DEF_PAIR_OF(dtype) \
typedef struct pair_of_##dtype { \
dtype first; \
dtype second; \
} pair_of_##dtype##_t
DEF_PAIR_OF(int);
DEF_PAIR_OF(double);
DEF_PAIR_OF(MyStruct);
/* etc */
Une autre chose qu’une fonction ne peut pas faire est de transformer les informations de compilation en informations d’exécution:
#ifdef DEBUG
#define REPORT_PTR_VALUE(v) printf("Pointer %s points to %p\n", #v, v)
#else
#define REPORT_PTR_VALUE(v)
#endif
void someFunction(const int* reallyCoolPointer) {
REPORT_PTR_VALUE(reallyCoolPointer);
/* Other code */
}
Il est impossible qu'une fonction utilise le nom de son paramètre dans sa sortie, comme le peut la macro. Cela illustre également la compilation de code de débogage pour les versions de version.
Les macros sont développées lors de la compilation. Vous avez raison de dire qu'ils sont souvent maltraités, mais un exemple classique en C serait d'avoir une macro pour écrire des messages de débogage qui (lorsque le mode débogage est désactivé au moment de la compilation) ne génère pas de code et ne provoque donc aucun ralentissement.
Parfois, vous souhaitez effectuer une journalisation, mais uniquement en mode débogage. Donc, vous écrivez quelque chose comme:
#ifdef DEBUG
#define LOG_MSG(x) printf(x);
#else
#define LOG_MSG(X)
#endif
Parfois, vous voulez simplement activer ou désactiver quelque chose en vous basant sur un commutateur au moment de la compilation. Par exemple, ma société co-brande notre produit et nos partenaires demandent de désactiver les choses. Nous allons donc faire quelque chose comme:
#ifndef SPECIAL_PARTNER_BUILD
DoSomethingReallyAwesome();
#endif
Générez ensuite avec -DSPECIAL_PARTNER_BUILD lorsque vous donnez des abandons au partenaire.
Parmi beaucoup d'autres raisons possibles.
Parfois, vous voulez juste sauvegarder votre frappe pour des choses que vous faites encore et encore. Certaines personnes vont trop loin ( toux MFC toux ) et écrivent ce qui revient à leur propre langage dans Macros pour résumer une API difficile. Cela fait du débogage un cauchemar frikkin.
Les macros peuvent avoir de nombreuses utilisations différentes de celles qui fonctionnent.
Il est très utile d’utiliser des macros pour tout type de nombre magique ou de chaîne.
#define MY_MAGIC_NUM 57
/*MY_MAGIC_NUM used all through out the code*/
Les macros C peuvent générer du code au moment de la compilation. Cela peut être (ab) utilisé pour créer efficacement des langages spécifiques à un domaine avec de nouveaux mots-clés et comportements.
Un de mes exemples préférés est la méthode de Simon Tatham consistant à implémenter des coroutines en C à travers des macros.
Nous avons utilisé ce type de macro dans notre code:
// convenience macros for implementing field setter/getter functions
#ifndef CREATE_GET_SET
#define CREATE_GET_SET(PREFIX, FIELD_NAME,FIELD_DATATYPE) \
protected:\
FIELD_DATATYPE PREFIX ## FIELD_NAME;\
public:\
inline FIELD_DATATYPE get ## _ ## FIELD_NAME(void) const\
{ \
return(PREFIX ## FIELD_NAME); \
} \
inline void set ## _ ## FIELD_NAME(FIELD_DATATYPE p) \
{ \
PREFIX ## FIELD_NAME = p; \
}
#endif
dans une classe/structure, vous définiriez une variable:
CREATE_GET_SET(_, id, unsigned int);
Cela définirait votre variable et créerait le getter/setter générique pour le code. Cela permet une génération de code plus propre et cohérente pour get/set. Bien sûr, vous pouvez tout écrire, mais cela fait beaucoup de code type passe-partout REMARQUE: il ne s'agit que d'une paire de macros. Je ne les ai pas tous postés. De cette façon, vous ne géreriez pas "char *" (où vous voulez que l’ensemble définisse strncpy ou strcpy les données). Ce n'était qu'une simple démonstration de ce que vous pouviez faire avec une macro et quelques types simples.
Je pense que le plus gros avantage vient lorsqu'on l'utilise comme "fichier de réglage"
#define USE_FAST_AGORHITM
//#define USE_SLOW_ONE
Et globales "constantes"
#define MAX_NUMBER_OF_USERS 100000
Il existe des programmes écrits principalement dans des macros (Recherchez l'implémentation AES de Brian Gladman, par exemple http://www.gladman.me.uk/cryptography_technology/index.php )
Dans les scénarios de performances intenses, vous pouvez utiliser des macros pour créer un déroulement de boucle "automatique". Les compilateurs modernes font probablement un meilleur travail avec cela cependant, mais c'était une application utile pour cela.
Le compilateur connaît souvent des détails sur la machine cible. Vous pouvez donc utiliser la compilation conditionnelle pour créer un code pour les processeurs big endian et un autre pour les processeurs little endian. Cela empêche le code d'être saturé de code conçu pour un processeur différent.
Un cas similaire se présente lorsque vous avez le code d'assemblage pour un système particulier, mais le code C pour les autres systèmes.
Sur les macros, le code de la fonction est inséré dans le flux de code de l'appelant. Cela peut, en fonction de nombreux autres facteurs, améliorer les performances, car l'optimiseur peut intégrer le code appelé dans la procédure - optimisez le code appelé dans l'appelant aux fonctions en ligne (si vous utilisez également le C++) et un peu de macros est . http://www.parashift.com/c++-faq-lite/inline-functions.html#faq-9.5