Comment concaténer deux fois avec le préprocesseur C et développer une macro comme dans «arg ## _ ## MACRO»?


152

J'essaie d'écrire un programme où les noms de certaines fonctions dépendent de la valeur d'une certaine variable macro avec une macro comme celle-ci:

#define VARIABLE 3
#define NAME(fun) fun ## _ ## VARIABLE

int NAME(some_function)(int a);

Malheureusement, la macro NAME()transforme cela en

int some_function_VARIABLE(int a);

plutôt que

int some_function_3(int a);

c'est donc clairement la mauvaise façon de procéder. Heureusement, le nombre de valeurs différentes possibles pour VARIABLE est petit, donc je peux simplement faire un #if VARIABLE == net lister tous les cas séparément, mais je me demandais s'il existe un moyen intelligent de le faire.


3
Êtes-vous sûr de ne pas vouloir utiliser de pointeurs de fonction à la place?
György Andrasek le

8
@Jurily - Les pointeurs de fonction fonctionnent à l'exécution, le préprocesseur fonctionne à (avant) la compilation. Il y a une différence, même si les deux peuvent être utilisés pour la même tâche.
Chris Lutz le

1
Le fait est qu'il est utilisé dans une bibliothèque de géométrie de calcul rapide .. qui est câblée pour une certaine dimension. Cependant, parfois, quelqu'un voudrait pouvoir l'utiliser avec quelques dimensions différentes (par exemple, 2 et 3) et il faudrait donc un moyen simple de générer du code avec des noms de fonction et de type dépendants des dimensions. De plus, le code est écrit en ANSI C, donc les trucs funky C ++ avec les modèles et la spécialisation ne sont pas applicables ici.
JJ.

2
Voter pour rouvrir parce que cette question est spécifique à l'expansion récursive des macros et stackoverflow.com/questions/216875/using-in-macros est un générique "à quoi ça sert". Le titre de cette question devrait être précisé.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

J'aurais aimé que cet exemple soit minimisé: il en va de même #define A 0 \n #define M a ## A: en avoir deux ##n'est pas la clé.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Réponses:


223

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 xsuivi 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_ 3comme 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.


1
Oui, cela résout le problème. Je connaissais le truc avec deux niveaux de récursion - j'ai dû jouer avec la stringification au moins une fois - mais je ne savais pas comment faire celui-ci.
JJ.

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 ##?
Robert Rüger

1
@ RobertRüger: cela double la longueur de la réponse, mais j'ai ajouté des informations à couvrir cpp -traditional. Notez qu'il n'y a pas de réponse définitive - cela dépend du préprocesseur que vous avez.
Jonathan Leffler

Merci beaucoup pour la réponse. C'est vraiment génial! En attendant, j'ai également trouvé une autre solution légèrement différente. Regardez ici . Il a également le problème que cela ne fonctionne pas avec clang. Heureusement, ce n'est pas un problème pour mon application ...
Robert Rüger

32
#define VARIABLE 3
#define NAME2(fun,suffix) fun ## _ ## suffix
#define NAME1(fun,suffix) NAME2(fun,suffix)
#define NAME(fun) NAME1(fun,VARIABLE)

int NAME(some_function)(int a);

Honnêtement, vous ne voulez pas savoir pourquoi cela fonctionne. Si vous savez pourquoi cela fonctionne, vous deviendrez ce gars au travail qui connaît ce genre de choses, et tout le monde viendra vous poser des questions. =)

Edit: si vous voulez vraiment savoir pourquoi cela fonctionne, je publierai volontiers une explication, en supposant que personne ne me batte.


Pourriez-vous expliquer pourquoi il a besoin de deux niveaux d'indirection. J'avais une réponse avec un niveau de redirection mais j'ai supprimé la réponse car je devais installer C ++ dans mon Visual Studio et cela ne fonctionnerait pas.
Cade Roux
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.