Préprocesseur C standard
$ cat xx.c
#define VARIABLE 3
#define PASTER(x,y) x ## _ ## y
#define EVALUATOR(x,y) PASTER(x,y)
#define NAME(fun) EVALUATOR(fun, VARIABLE)
extern void NAME(mine)(char *x);
$ gcc -E xx.c
# 1 "xx.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "xx.c"
extern void mine_3(char *x);
$
Deux niveaux d'indirection
Dans un commentaire sur une autre réponse, Cade Roux a demandé pourquoi cela nécessite deux niveaux d'indirection. La réponse désinvolte est que c'est ainsi que la norme l'exige; vous avez tendance à trouver que vous avez également besoin de l'astuce équivalente avec l'opérateur stringizing.
La section 6.10.3 de la norme C99 couvre le «remplacement de macros» et 6.10.3.1 la «substitution d'arguments».
Une fois que les arguments pour l'invocation d'une macro de type fonction ont été identifiés, la substitution d'argument a lieu. Un paramètre dans la liste de remplacement, à moins précédée d'une #
ou de ##
prétraitement jeton ou suivi d'un ##
jeton de prétraitement (voir ci - dessous), est remplacé par l'argument correspondant après que toutes les macros qui y sont contenues ont été étendues. Avant d'être substitués, les jetons de prétraitement de chaque argument sont complètement remplacés par macro comme s'ils formaient le reste du fichier de prétraitement; aucun autre jeton de prétraitement n'est disponible.
Dans l'invocation NAME(mine)
, l'argument est «mien»; il est entièrement étendu au «mien»; il est ensuite remplacé dans la chaîne de remplacement:
EVALUATOR(mine, VARIABLE)
Maintenant, la macro EVALUATOR est découverte, et les arguments sont isolés comme «mien» et «VARIABLE»; ce dernier est ensuite entièrement développé en '3', et remplacé dans la chaîne de remplacement:
PASTER(mine, 3)
Son fonctionnement est couvert par d'autres règles (6.10.3.3 'L'opérateur ##'):
Si, dans la liste de remplacement d'une macro de type fonction, un paramètre est immédiatement précédé ou suivi d'un ##
jeton de prétraitement, le paramètre est remplacé par la séquence de jetons de prétraitement de l'argument correspondant; [...]
Pour les appels de macro de type objet et fonction, avant que la liste de remplacement ne soit réexaminée pour plus de noms de macro à remplacer, chaque instance d'un ##
jeton de prétraitement dans la liste de remplacement (pas à partir d'un argument) est supprimée et le jeton de prétraitement précédent est concaténé avec le jeton de prétraitement suivant.
Ainsi, la liste de remplacement contient x
suivi de ##
et également ##
suivi de y
; donc nous avons:
mine ## _ ## 3
et l'élimination des ##
jetons et la concaténation des jetons de chaque côté combine «mien» avec «_» et «3» pour donner:
mine_3
C'est le résultat souhaité.
Si nous regardons la question originale, le code était (adapté pour utiliser 'mine' au lieu de 'some_function'):
#define VARIABLE 3
#define NAME(fun) fun ## _ ## VARIABLE
NAME(mine)
L'argument de NAME est clairement «le mien» et il est entièrement développé.
En suivant les règles du 6.10.3.3, on trouve:
mine ## _ ## VARIABLE
qui, lorsque les ##
opérateurs sont éliminés, correspond à:
mine_VARIABLE
exactement comme indiqué dans la question.
Préprocesseur C traditionnel
Robert Rüger demande :
Y a-t-il un moyen de faire cela avec le préprocesseur C traditionnel qui n'a pas l'opérateur de collage de jetons ##
?
Peut-être, et peut-être pas - cela dépend du préprocesseur. L'un des avantages du préprocesseur standard est qu'il dispose de cette fonctionnalité qui fonctionne de manière fiable, alors qu'il existait différentes implémentations pour les préprocesseurs pré-standard. Une condition est que lorsque le préprocesseur remplace un commentaire, il ne génère pas d'espace comme le préprocesseur ANSI est tenu de le faire. Le préprocesseur GCC (6.3.0) C satisfait à cette exigence; le préprocesseur Clang de XCode 8.2.1 ne le fait pas.
Quand cela fonctionne, cela fait le travail ( x-paste.c
):
#define VARIABLE 3
#define PASTE2(x,y) x/**/y
#define EVALUATOR(x,y) PASTE2(PASTE2(x,_),y)
#define NAME(fun) EVALUATOR(fun,VARIABLE)
extern void NAME(mine)(char *x);
Notez qu'il n'y a pas d'espace entre fun,
et VARIABLE
- c'est important car s'il est présent, il est copié dans la sortie et vous vous retrouvez avec mine_ 3
comme nom, ce qui n'est pas syntaxiquement valide, bien sûr. (Maintenant, s'il vous plaît, puis-je avoir mes cheveux en arrière?)
Avec GCC 6.3.0 (en cours d'exécution cpp -traditional x-paste.c
), j'obtiens:
# 1 "x-paste.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "x-paste.c"
extern void mine_3(char *x);
Avec Clang de XCode 8.2.1, j'obtiens:
# 1 "x-paste.c"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 329 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "x-paste.c" 2
extern void mine _ 3(char *x);
Ces espaces gâchent tout. Je note que les deux préprocesseurs sont corrects; différents préprocesseurs pré-standard présentaient les deux comportements, ce qui faisait du collage de jetons un processus extrêmement ennuyeux et peu fiable lors de la tentative de portage du code. La norme avec la ##
notation simplifie radicalement cela.
Il pourrait y avoir d'autres façons de faire cela. Cependant, cela ne fonctionne pas:
#define VARIABLE 3
#define PASTER(x,y) x/**/_/**/y
#define EVALUATOR(x,y) PASTER(x,y)
#define NAME(fun) EVALUATOR(fun,VARIABLE)
extern void NAME(mine)(char *x);
GCC génère:
# 1 "x-paste.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "x-paste.c"
extern void mine_VARIABLE(char *x);
Proche, mais pas de dés. YMMV, bien sûr, en fonction du préprocesseur pré-standard que vous utilisez. Franchement, si vous êtes coincé avec un préprocesseur qui ne coopère pas, il serait probablement plus simple de prendre des dispositions pour utiliser un préprocesseur C standard à la place du préprocesseur pré-standard (il existe généralement un moyen de configurer le compilateur de manière appropriée) que de passer beaucoup de temps à essayer de trouver un moyen de faire le travail.