J'ai écrit un peu deC, et je le lis assez bien pour avoir une idée générale de ce qu'il fait, mais chaque fois que j'ai rencontré une macro, cela m'a complètement jeté. Je finis par devoir me rappeler ce qu'est la macro et la remplacer dans ma tête au fur et à mesure que je lisais. Celles que j'ai rencontrées, intuitives et faciles à comprendre, ressemblaient toujours à de petites mini-fonctions. Je me suis donc toujours demandé pourquoi elles n'étaient pas simplement des fonctions.
Je peux comprendre la nécessité de définir différents types de construction pour les versions de débogage ou inter-plateformes dans le pré-processeur, mais la possibilité de définir des substitutions arbitraires semble utile uniquement pour rendre un langage déjà difficile encore plus difficile à comprendre.
Pourquoi un préprocesseur aussi complexe a-t-il été introduit pourC? Et est-ce que quelqu'un a un exemple d'utilisation qui me fera comprendre pourquoi il semble toujours être utilisé à des fins autres que simples si des compilations conditionnelles de style #debug?
Modifier:
Après avoir lu un certain nombre de réponses, je ne comprends toujours pas. La réponse la plus courante consiste à utiliser du code en ligne. Si le mot clé en ligne ne le fait pas, il a soit une bonne raison de ne pas le faire, soit la mise en œuvre doit être corrigée. Je ne comprends pas pourquoi un mécanisme complètement différent est nécessaire, ce qui signifie "vraiment inline ce code" (à part le code en cours d'écriture avant qu'inline ne soit là). Je ne comprends pas non plus l’idée évoquée selon laquelle "s’il est trop bête de se mettre à la place". Il est certainement préférable de placer une fonction dans un morceau de code prenant une entrée et produisant une sortie. Je pense ne pas l'obtenir parce que je ne suis pas habitué aux micro-optimisations de l'écritureC, mais le préprocesseur semble être une solution complexe à quelques problèmes simples.
Je finis par devoir me rappeler ce qu'est la macro et la remplacer dans ma tête au fur et à mesure que je lisais.
Cela semble mal refléter la dénomination des macros. Je suppose que vous n’auriez pas à émuler le préprocesseur s’il s’agissait d’une macro log_function_entry()
.
Celles que j’ai rencontrées qui étaient intuitives et faciles à comprendre ressemblaient toujours à de petites mini-fonctions, je me suis donc toujours demandé pourquoi elles n’étaient pas uniquement des fonctions.
Ils devraient normalement l'être, à moins qu'ils n'aient besoin d'opérer sur des paramètres génériques.
#define max(a,b) ((a)<(b)?(b):(a))
fonctionnera sur tout type avec un opérateur <
.
Plus que des fonctions, les macros vous permettent d'effectuer des opérations en utilisant les symboles du fichier source. Cela signifie que vous pouvez créer un nouveau nom de variable ou référencer le fichier source et le numéro de ligne sur lequel se trouve la macro.
En C99, les macros permettent également d’appeler des fonctions variadiques telles que printf
#define log_message(guard,format,...) \
if (guard) printf("%s:%d: " format "\n", __FILE__, __LINE__,__VA_ARGS_);
log_message( foo == 7, "x %d", x)
Dans lequel le format fonctionne comme printf. Si la valeur de garde est vraie, elle envoie le message avec le fichier et le numéro de ligne qui l'a imprimé. S'il s'agissait d'un appel de fonction, il ne connaîtrait pas le fichier ni la ligne à partir de laquelle vous l'avez appelé, et utiliser vaprintf
serait un peu plus de travail.
Cet extrait résume assez bien mon point de vue sur le sujet, en comparant plusieurs manières d'utiliser les macros C
et la manière de les implémenter dans D
.
À l’époque où
C
a été inventé, compilateur la technologie était primitive. Installation d'un préprocesseur de macro de texte sur le devant fin était un moyen simple et facile d'ajouter de nombreuses fonctionnalités puissantes. Le augmentation de la taille et de la complexité de les programmes ont montré que ces les fonctionnalités viennent avec de nombreux inhérents problèmes.D
n'a pas de pré-processeur; maisD
fournit un plus des moyens évolutifs pour résoudre le même problème problèmes.
Les macros de préprocesseur ajoutent des fonctionnalités puissantes et une flexibilité accrue à C
. Mais ils ont un inconvénient:
#include
', englobe des dizaines de milliers de lignes de définitions de macros, il devient problématique d'éviter des extensions de macros par inadvertance.C++
.)Voici une énumération des utilisations courantes des macros et de la fonctionnalité correspondante dans D:
Définir les constantes littérales:
La manière C
de préprocesseur
#define VALUE 5
La manière D
const int VALUE = 5;
Création d'une liste de valeurs ou de drapeaux:
La manière C
de préprocesseur
int flags:
#define FLAG_X 0x1
#define FLAG_Y 0x2
#define FLAG_Z 0x4
...
flags |= FLAG_X;
La manière D
enum FLAGS { X = 0x1, Y = 0x2, Z = 0x4 };
FLAGS flags;
...
flags |= FLAGS.X;
Définition des conventions d'appel de fonction:
La manière C
de préprocesseur
#ifndef _CRTAPI1
#define _CRTAPI1 __cdecl
#endif
#ifndef _CRTAPI2
#define _CRTAPI2 __cdecl
#endif
int _CRTAPI2 func();
La manière D
Les conventions d’appel peuvent être spécifiées en blocs, il n’est donc pas nécessaire de les changer pour chaque fonction:
extern (Windows)
{
int onefunc();
int anotherfunc();
}
Programmation générique simple:
La manière C
de préprocesseur
Sélection de la fonction à utiliser en fonction de la substitution de texte:
#ifdef UNICODE
int getValueW(wchar_t *p);
#define getValue getValueW
#else
int getValueA(char *p);
#define getValue getValueA
#endif
La manière D
D
active les déclarations de symboles qui sont des alias d'autres symboles:
version (UNICODE)
{
int getValueW(wchar[] p);
alias getValueW getValue;
}
else
{
int getValueA(char[] p);
alias getValueA getValue;
}
Il existe d'autres exemples sur le site Web DigitalMars .
C’est un langage de programmation (plus simple) au-dessus de C; ils sont donc utiles pour effectuer des métaprogrammations au moment de la compilation. En d’autres termes, vous pouvez écrire du code macro qui génère du code C en moins de lignes et de temps. l'écrire directement en C.
Ils sont également très utiles pour écrire des expressions "polymorphes" ou "surchargées" "fonctionnant comme". par exemple. une macro max définie comme:
#define max(a,b) ((a)>(b)?(a):(b))
est utile pour tout type numérique; et en C vous ne pouviez pas écrire:
int max(int a, int b) {return a>b?a:b;}
float max(float a, float b) {return a>b?a:b;}
double max(double a, double b) {return a>b?a:b;}
...
même si vous vouliez, car vous ne pouvez pas surcharger les fonctions.
Et pour ne pas mentionner la compilation conditionnelle et les fichiers inclus (qui font également partie du langage macro) ...
Les macros permettent à quelqu'un de modifier le comportement du programme pendant la compilation. Considère ceci:
Au moment de la compilation, cela signifie que le code non utilisé ne sera même pas intégré au binaire et que le processus de construction peut modifier les valeurs, à condition qu'il soit intégré au préprocesseur de macro. Exemple: make Arch = arm (suppose que la définition de macro de transmission est cc -DARCH = arm)
Exemples simples: .__ (à partir de la glibc limits.h, définissez la plus grande valeur de long)
#if __WORDSIZE == 64
#define LONG_MAX 9223372036854775807L
#else
#define LONG_MAX 2147483647L
#endif
Vérifie (en utilisant le #define __WORDSIZE) lors de la compilation si nous compilons pour 32 ou 64 bits. Avec une chaîne d’outils multilib, l’utilisation des paramètres -m32 et -m64 peut changer automatiquement la taille des bits.
(Demande de version POSIX)
#define _POSIX_C_SOURCE 200809L
Demandes pendant la compilation Prise en charge de POSIX 2008. La bibliothèque standard peut prendre en charge de nombreux standards (incompatibles), mais avec cette définition, elle fournira les prototypes de fonctions corrects (exemple: getline (), no gets (), etc.). Si la bibliothèque ne prend pas en charge la norme, elle peut générer une erreur # pendant la compilation, au lieu de se bloquer lors de l'exécution, par exemple.
(chemin codé en dur)
#ifndef LIBRARY_PATH
#define LIBRARY_PATH "/usr/lib"
#endif
Définit, pendant la compilation, un répertoire hardcode. Pourrait être changé avec -DLIBRARY_PATH =/home/user/lib, par exemple. S'il s'agissait d'un const char *, comment le configureriez-vous lors de la compilation?
(pthread.h, définitions complexes à la compilation)
# define PTHREAD_MUTEX_INITIALIZER \
{ { 0, 0, 0, 0, 0, 0, { 0, 0 } } }
Les gros morceaux de texte peuvent être déclarés (toujours au moment de la compilation). Cela n'est pas possible avec des fonctions ou des constantes (au moment de la compilation).
Pour éviter de vraiment compliquer les choses et pour éviter de suggérer des styles de codage médiocres, je ne donnerai pas d'exemple de code compilé sous différents systèmes d'exploitation incompatibles. Utilisez votre système cross-build pour cela, mais il devrait être clair que le pré-processeur le permet sans l'aide du système de compilation, sans interrompre la compilation en raison d'interfaces absentes.
Enfin, réfléchissez à l’importance de la compilation conditionnelle sur les systèmes embarqués, où la vitesse du processeur et la mémoire sont limitées et les systèmes très hétérogènes.
Maintenant, si vous le demandez, est-il possible de remplacer toutes les définitions de constante de macro et les appels de fonction par les définitions appropriées? La réponse est oui, mais cela ne fera pas disparaître la nécessité de changer le comportement du programme pendant la compilation. Le pré-processeur serait toujours nécessaire.
Rappelez-vous que les macros (et le pré-processeur) remontent aux premiers jours de C. Ils étaient jadis le SEUL moyen de faire des "fonctions" en ligne (car, bien sûr, inline est un mot-clé très récent) et reste le seul. seul moyen de forcer quelque chose à être en ligne.
En outre, les macros sont le seul moyen de réaliser des astuces telles que l’insertion du fichier et de la ligne dans les constantes de chaîne au moment de la compilation.
De nos jours, beaucoup de choses que les macros étaient la seule façon de faire sont mieux gérées par le biais de mécanismes plus récents. Mais ils ont toujours leur place, de temps en temps.
Outre l'inline pour l'efficacité et la compilation conditionnelle, les macros peuvent être utilisées pour augmenter le niveau d'abstraction du code C de bas niveau. C ne vous isole pas vraiment des détails essentiels de la gestion de la mémoire et des ressources et de la présentation exacte des données, et ne prend en charge que des formes très limitées de masquage d'informations et d'autres mécanismes de gestion de grands systèmes. Avec les macros, vous n'êtes plus obligé d'utiliser uniquement les constructions de base en langage C: vous pouvez définir vos propres structures de données et constructions de codage (y compris les classes et les modèles!) Tout en écrivant nominalement C!
Les macros de préprocesseur proposent en réalité un langage Turing-complete exécuté au moment de la compilation. L’un des exemples les plus impressionnants (et un peu effrayant) de ce phénomène est du côté C++: la bibliothèque Boost Preprocessor utilise le préprocesseur C99 / C++ 98 pour construire (relativement) en toute sécurité des constructions de programmation qui sont ensuite étendues aux déclarations sous-jacentes et au code que vous entrez, que ce soit en C ou en C++.
En pratique, je recommanderais de considérer la programmation pré-processeur en dernier recours, lorsque vous n’avez pas la latitude d’utiliser des constructions de haut niveau dans des langues plus sûres. Mais parfois, il est bon de savoir ce que vous pouvez faire si votre dos est contre le mur et que les belettes se referment ...!
J'ai vu cet extrait de code dans de nombreux programmes de jeu gratuits pour UNIX:
/ *
* Valeurs de bits.
* /
#define BIT_0 1
#define BIT_1 2
#define BIT_2 4
#define BIT_3 8
#define BIT_4 16
#define BIT_5 32
#define BIT_6 64
#define BIT_7 128
#define BIT_8 256
#define BIT_9 512
#define BIT_10 1024
#define BIT_11 2048
#define BIT_12 4096
#define BIT_13 8192
#define BIT_14 16384
#define BIT_15 32768
#define BIT_16 65536
#define BIT_17 131072
#define BIT_18 262144
#define BIT_19 524288
#define BIT_20 1048576
#define BIT_21 2097152
#define BIT_22 4194304
#define BIT_23 8388608
#define BIT_24 16777216
#define BIT_25 33554432
#define BIT_26 67108864
#define BIT_27 134217728
#define BIT_28 268435456
#define BIT_29 536870912
#define BIT_30 1073741824
#define BIT_31 2147483648Un moyen beaucoup plus simple d'y parvenir est:
#define BIT_0 0x00000001
#define BIT_1 0x00000002
#define BIT_2 0x00000004
#define BIT_3 0x00000008
#define BIT_4 0x00000010
...
#define BIT_28 0x10000000
#define BIT_29 0x20000000
#define BIT_30 0x40000000
#define BIT_31 0x80000000Un moyen plus simple consiste toujours à laisser le compilateur faire les calculs:
#define BIT_0 (1)
#define BIT_1 (1 << 1)
#define BIT_2 (1 << 2)
#define BIT_3 (1 << 3)
#define BIT_4 (1 << 4)
...
#define BIT_28 (1 << 28)
#define BIT_29 (1 << 29)
#define BIT_30 (1 << 30)
#define BIT_31 (1 << 31)Mais pourquoi se donner la peine de définir 32 constantes? Le langage C a également paramétré des macros. Tout ce dont vous avez vraiment besoin est:
#define BIT (x) (1 << (x))
Quoi qu'il en soit, je me demande si le gars qui a écrit le code original a utilisé une calculatrice ou a tout simplement calculé le tout sur papier.
Ce n'est qu'une utilisation possible des macros.
J'ajouterai à ce qui a déjà été dit.
Étant donné que les macros fonctionnent sur les substitutions de texte, elles vous permettent de réaliser des tâches très utiles qu'il serait impossible d'utiliser avec des fonctions.
Voici quelques cas où les macros peuvent être vraiment utiles:
/* Get the number of elements in array 'A'. */
#define ARRAY_LENGTH(A) (sizeof(A) / sizeof(A[0]))
C'est une macro très populaire et fréquemment utilisée. Ceci est très pratique lorsque, par exemple, vous devez parcourir un tableau.
int main(void)
{
int a[] = {1, 2, 3, 4, 5};
int i;
for (i = 0; i < ARRAY_LENGTH(a); ++i) {
printf("a[%d] = %d\n", i, a[i]);
}
return 0;
}
Ici, peu importe si un autre programmeur ajoute cinq éléments supplémentaires à a
dans la décleration. La boucle for
- fera toujours parcourera tous les éléments.
Les fonctions de la bibliothèque C pour comparer la mémoire et les chaînes sont plutôt laides à utiliser.
Vous écrivez:
char *str = "Hello, world!";
if (strcmp(str, "Hello, world!") == 0) {
/* ... */
}
ou
char *str = "Hello, world!";
if (!strcmp(str, "Hello, world!")) {
/* ... */
}
Pour vérifier si str
pointe sur "Hello, world"
. Personnellement, je pense que ces deux solutions semblent très laides et déroutantes (en particulier !strcmp(...)
).
Voici deux macros ordonnées que certaines personnes (y compris I) utilisent pour comparer des chaînes ou de la mémoire à l'aide de strcmp
/memcmp
:
/* Compare strings */
#define STRCMP(A, o, B) (strcmp((A), (B)) o 0)
/* Compare memory */
#define MEMCMP(A, o, B) (memcmp((A), (B)) o 0)
Maintenant, vous pouvez maintenant écrire le code comme ceci:
char *str = "Hello, world!";
if (STRCMP(str, ==, "Hello, world!")) {
/* ... */
}
Voici l'intention beaucoup plus claire!
Ce sont des cas où les macros sont utilisées pour des tâches que les fonctions ne peuvent accomplir. Les macros ne doivent pas être utilisées pour remplacer des fonctions, mais elles ont d’autres utilisations intéressantes.
L'un des cas où les macros sont vraiment brillantes est la génération de code avec celles-ci.
J'avais l'habitude de travailler sur un ancien système C++ qui utilisait un système de plug-in avec sa propre méthode pour transmettre des paramètres au plug-in (Utilisation d'une structure personnalisée de type map). Quelques macros simples ont été utilisées pour pouvoir traiter cette bizarrerie et nous ont permis d'utiliser de vraies classes et fonctions C++ avec des paramètres normaux dans les plugins sans trop de problèmes. Tout le code de collage est généré par les macros.
Compte tenu des commentaires que vous avez formulés dans votre question, vous n’allez peut-être pas vraiment comprendre que le fait d’appeler une fonction peut entraîner des frais généraux considérables. Il peut être nécessaire de copier les paramètres et les registres de clé dans la pile lors de l’entrée et de la désenrouler lors de la sortie. Cela était particulièrement vrai pour les anciens processeurs Intel. Les macros permettent au programmeur de conserver (presque) l'abstraction d'une fonction, tout en évitant le surcoût coûteux d'un appel de fonction. Le mot-clé inline est consultatif, mais le compilateur peut ne pas toujours réussir. La gloire et le danger de 'C' est que vous pouvez généralement plier le compilateur à votre guise.
La programmation quotidienne de ce type de micro-optimisation (en évitant les appels de fonction) est généralement pire qu’inutile, mais si vous écrivez une fonction critique en temps appelée par le noyau d’un système cela peut faire une énorme différence.
C'est bon pour le code en ligne et éviter les frais généraux d'appels de fonction. En plus de l'utiliser si vous souhaitez modifier le comportement ultérieurement sans modifier de nombreux lieux. Ce n'est pas utile pour les choses complexes, mais pour les simples lignes de code que vous voulez insérer, ce n'est pas mal.
Contrairement aux fonctions habituelles, vous pouvez contrôler le flux (si, tant que, pour, ...) dans les macros. Voici un exemple:
#include <stdio.h>
#define Loop(i,x) for(i=0; i<x; i++)
int main(int argc, char *argv[])
{
int i;
int x = 5;
Loop(i, x)
{
printf("%d", i); // Output: 01234
}
return 0;
}
Les macros vous permettent de vous débarrasser des fragments copiés, que vous ne pouvez éliminer de toute autre manière.
Par exemple (le code réel, la syntaxe du compilateur VS 2010):
for each (auto entry in entries)
{
sciter::value item;
item.set_item("DisplayName", entry.DisplayName);
item.set_item("IsFolder", entry.IsFolder);
item.set_item("IconPath", entry.IconPath);
item.set_item("FilePath", entry.FilePath);
item.set_item("LocalName", entry.LocalName);
items.append(item);
}
C'est l'endroit où vous passez une valeur de champ sous le même nom dans un moteur de script. Est-ce copié-collé? Oui. DisplayName
est utilisé comme chaîne pour un script et comme nom de champ pour le compilateur. Est-ce mauvais? Oui. Si vous refactorisez votre code et renommez LocalName
en RelativeFolderName
(comme je le faisais) et oubliez de faire la même chose avec la chaîne (comme je l'ai fait), le script fonctionnera d'une manière inattendue dépend de ce que vous avez oublié de renommer le champ dans un fichier de script séparé, mais si le script est utilisé pour la sérialisation, il s'agirait d'un bogue de 100%).
Si vous utilisez une macro pour cela, il n'y aura pas de place pour le bogue:
for each (auto entry in entries)
{
#define STR_VALUE(arg) #arg
#define SET_ITEM(field) item.set_item(STR_VALUE(field), entry.field)
sciter::value item;
SET_ITEM(DisplayName);
SET_ITEM(IsFolder);
SET_ITEM(IconPath);
SET_ITEM(FilePath);
SET_ITEM(LocalName);
#undef SET_ITEM
#undef STR_VALUE
items.append(item);
}
Malheureusement, cela ouvre la porte à d'autres types de bugs. Vous pouvez créer une faute de frappe en écrivant la macro et ne jamais voir un code gâté, car le compilateur n'affiche pas son apparence après le prétraitement. Quelqu'un d'autre pourrait utiliser le même nom (c'est pourquoi je «libère» les macros dès que possible avec #undef
). Alors, utilisez-le judicieusement. Si vous voyez une autre façon de vous débarrasser du code copié-collé (tel que des fonctions), utilisez cette méthode. Si vous constatez que supprimer le code copié-collé avec des macros ne vaut pas la peine, conservez le code copié-collé.
En exploitant la manipulation de texte du préprocesseur C, on peut construire l'équivalent C d'une structure de données polymorphe. En utilisant cette technique, nous pouvons construire une boîte à outils fiable de structures de données primitives pouvant être utilisées dans n'importe quel programme C, car elles exploitent la syntaxe C et non les spécificités d'une implémentation particulière.
Des explications détaillées sur l'utilisation des macros pour la gestion de la structure de données sont données ici - http://multi-core-dump.blogspot.com/2010/11/interesting-use-of-c-macros-polymorphic.html
L'une des raisons évidentes est qu'en utilisant une macro, le code sera développé lors de la compilation et vous obtiendrez un pseudo appel de fonction sans la surcharge de l'appel.
Sinon, vous pouvez également l'utiliser pour les constantes symboliques, de sorte que vous n'ayez pas à modifier la même valeur à plusieurs endroits pour modifier une petite chose.
Macros .. lorsque votre & # (* $ & compilateur refuse tout simplement d’aligner quelque chose).
Cela devrait être une affiche de motivation, non?
Très sérieusement, google abus de préprocesseur (vous pouvez voir une question similaire SO comme résultat # 1). Si j'écris une macro qui va au-delà de la fonctionnalité de assert (), j'essaie généralement de voir si mon compilateur intégrerait réellement une fonction similaire.
D'autres s'opposeront à l'utilisation de #if pour la compilation conditionnelle. Ils préféreraient que vous:
if (RUNNING_ON_VALGRIND)
plutôt que
#if RUNNING_ON_VALGRIND
.. à des fins de débogage, puisque vous pouvez voir if () mais pas #if dans un débogueur. Ensuite, nous plongeons dans #ifdef vs #if.
Si c'est moins de 10 lignes de code, essayez de le mettre en ligne. Si ce n'est pas possible, essayez de l'optimiser. Si c'est trop bête pour être une fonction, créez une macro.
Bien que je ne sois pas un grand fan des macros et que je n’ai plus tendance à écrire beaucoup de C, sur la base de mes tâches actuelles, une chose comme celle-ci (qui pourrait évidemment avoir des effets secondaires) est pratique:
#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))
Maintenant, je n'ai rien écrit de la sorte depuis des années, mais de telles «fonctions» recouvraient tout le code que je maintenais plus tôt dans ma carrière. Je suppose que l'expansion pourrait être considérée comme pratique.