web-dev-qa-db-fra.com

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

J'essaie d'écrire un programme dans lequel les noms de certaines fonctions dépendent de la valeur d'une variable de macro donnée 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 s'y prendre. Heureusement, le nombre de valeurs possibles pour VARIABLE est faible, je peux donc simplement faire un #if VARIABLE == n Et répertorier tous les cas séparément, mais je me demandais s’il existe un moyen astucieux de le faire.

137
JJ.

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 Rouxdemandé pourquoi cela nécessite deux niveaux d'indirection. La réponse désinvolte est que c'est ainsi que la norme l'exige pour fonctionner. vous avez tendance à constater que vous avez également besoin de la même astuce avec l'opérateur stringizing.

La section 6.10.3 de la norme C99 couvre le "remplacement de macro" et la 6.10.3.1, le "remplacement d'argument".

Une fois que les arguments d'invocation d'une macro de type fonction ont été identifiés, la substitution d'argument est effectuée. Un paramètre de la liste de remplacement, sauf s'il est précédé d'un jeton de prétraitement # Ou ## Ou suivi d'un jeton de prétraitement ## (Voir ci-dessous), est remplacé par l'argument correspondant après 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 une 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 complètement développé pour "mien"; il est ensuite substitué dans la chaîne de remplacement:

EVALUATOR(mine, VARIABLE)

Maintenant, la macro EVALUATOR est découverte et les arguments sont isolés en tant que "mine" et "VARIABLE"; ce dernier est alors complètement étendu à "3" et remplacé par la chaîne de remplacement:

PASTER(mine, 3)

Le fonctionnement de celui-ci est couvert par d'autres règles (6.10.3.3 "L'opérateur ##"):

Si, dans la liste de remplacement d’une macro semblable à une 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 invocations de macros semblables à des objets et à des fonctions, avant que la liste de remplacement ne soit réexaminée pour remplacer plusieurs 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.

La liste de remplacement contient donc x suivi de ## Et de ## Suivi de y; nous avons donc:

mine ## _ ## 3

et en éliminant les jetons ## et en concaténant les jetons de chaque côté, associez "mine" à "_" et "3" pour obtenir:

mine_3

C'est le résultat souhaité.


Si nous examinons la question initiale, le code était (adapté pour utiliser "mine" au lieu de "une_fonction"):

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

NAME(mine)

L'argument de NAME est clairement "mien" et il est complètement développé.
En suivant les règles de 6.10.3.3, on trouve:

mine ## _ ## VARIABLE

qui, lorsque les opérateurs ## sont supprimés, mappent sur:

mine_VARIABLE

exactement comme indiqué dans la question.


Préprocesseur C traditionnel

Robert Rügerdemande :

Y a-t-il moyen de faire cela avec le préprocesseur C traditionnel qui ne possède pas l'opérateur de collage de jeton ##?

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 des conditions est que lorsque le préprocesseur remplace un commentaire, il ne génère pas d'espace comme le préprocesseur ANSI doit le faire. Le préprocesseur GCC (6.3.0) C répond à 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 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 (avec cpp -traditional x-paste.c), Je reçois:

# 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, je reçois:

# 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 remarque que les deux pré-processeurs sont corrects; différents préprocesseurs pré-standards présentaient les deux comportements, ce qui rendait le collage de jetons extrêmement fastidieux et peu fiable lorsque vous tentiez de transférer du code. La norme avec la notation ## Simplifie radicalement cela.

Il pourrait y avoir d'autres moyens de le faire. 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 d’utiliser un pré-processeur standard à la place du pré-standard (il existe généralement un moyen de configurer le compilateur de manière appropriée) plutôt que de passer beaucoup de temps à essayer de trouver un moyen de faire le travail.

205
Jonathan Leffler
#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-là au travail qui connaît ce genre de choses et tout le monde viendra vous poser des questions. =)

Edit: Si vous voulez réellement savoir pourquoi cela fonctionne, je posterai volontiers une explication, en supposant que personne ne me bat.

32
Stephen Canon