¿Cómo puedo concatenar dos veces con el preprocesador de C y expandir una macro como en "arg ## _ ## MACRO"?

Resuelto JJ. asked hace 14 años • 0 respuestas

Estoy intentando escribir un programa donde los nombres de algunas funciones dependan del valor de una determinada macro variable con una macro como esta:

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

int NAME(some_function)(int a);

Desafortunadamente, la macro NAME()convierte eso en

int some_function_VARIABLE(int a);

en vez de

int some_function_3(int a);

así que esta es claramente la forma equivocada de hacerlo. Afortunadamente, la cantidad de valores posibles diferentes para VARIABLE es pequeña, por lo que simplemente puedo hacer una #if VARIABLE == nlista de todos los casos por separado, pero ¿existe una forma inteligente de hacerlo?

JJ. avatar Sep 29 '09 07:09 JJ.
Aceptado

Preprocesador C estándar

$ 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);
$

Dos niveles de indirección

En un comentario a otra respuesta, Cade Roux preguntó por qué esto necesita dos niveles de indirección. La respuesta frívola es porque así es como el estándar exige que funcione; tiendes a descubrir que también necesitas el truco equivalente con el operador de encordado.

La sección 6.10.3 del estándar C99 cubre la "reemplazo de macros" y la 6.10.3.1 cubre la "sustitución de argumentos".

Una vez identificados los argumentos para la invocación de una macro similar a una función, se produce la sustitución de argumentos. Un parámetro en la lista de reemplazo, a menos que esté precedido por un #token ##de preprocesamiento o seguido de un ##token de preprocesamiento (ver más abajo), se reemplaza por el argumento correspondiente después de que se hayan expandido todas las macros contenidas en él. Antes de ser sustituidos, los tokens de preprocesamiento de cada argumento se reemplazan completamente como si formaran el resto del archivo de preprocesamiento; no hay otros tokens de preprocesamiento disponibles.

En la invocación NAME(mine), el argumento es 'mío'; está completamente expandido a 'mío'; luego se sustituye en la cadena de reemplazo:

EVALUATOR(mine, VARIABLE)

Ahora se descubre el macro EVALUADOR y los argumentos se aíslan como 'míos' y 'VARIABLE'; este último luego se expande completamente a '3' y se sustituye en la cadena de reemplazo:

PASTER(mine, 3)

El funcionamiento de esto está cubierto por otras reglas (6.10.3.3 'El operador ##'):

Si, en la lista de reemplazo de una macro similar a una función, un parámetro está inmediatamente precedido o seguido por un ##token de preprocesamiento, el parámetro se reemplaza por la secuencia del token de preprocesamiento del argumento correspondiente; [...]

Para invocaciones de macros tanto de tipo objeto como de función, antes de volver a examinar la lista de reemplazo para buscar más nombres de macros para reemplazar, cada instancia de un ##token de preprocesamiento en la lista de reemplazo (no de un argumento) se elimina y el token de preprocesamiento anterior se concatena. con el siguiente token de preprocesamiento.

Entonces, la lista de reemplazo contiene xseguido de ##y también ##seguido de y; entonces tenemos:

mine ## _ ## 3

y eliminar los ##tokens y concatenar los tokens en ambos lados combina 'mío' con '_' y '3' para producir:

mine_3

Este es el resultado deseado.


Si miramos la pregunta original, el código era (adaptado para usar 'mío' en lugar de 'alguna_función'):

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

NAME(mine)

El argumento de NOMBRE es claramente "mío" y está completamente ampliado.
Siguiendo las reglas del 6.10.3.3, encontramos:

mine ## _ ## VARIABLE

que, cuando ##se eliminan los operadores, se asigna a:

mine_VARIABLE

exactamente como se informa en la pregunta.


Preprocesador C tradicional

Robert Rüger pregunta :

¿Hay alguna manera de hacer esto con el preprocesador C tradicional que no tiene el operador de pegado de tokens ##?

Tal vez sí, tal vez no; depende del preprocesador. Una de las ventajas del preprocesador estándar es que tiene esta función que funciona de manera confiable, mientras que existían diferentes implementaciones para los preprocesadores preestándar. Un requisito es que cuando el preprocesador reemplaza un comentario, no genere un espacio como debe hacerlo el preprocesador ANSI. El preprocesador GCC (6.3.0) C cumple con este requisito; el preprocesador Clang de XCode 8.2.1 no lo hace.

Cuando funciona, esto hace el trabajo ( 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);

Tenga en cuenta que no hay un espacio entre fun,y VARIABLE; eso es importante porque, si está presente, se copia en la salida y termina con mine_ 3el nombre, lo cual no es sintácticamente válido, por supuesto. (Ahora, ¿puedo recuperarme el pelo?)

Con GCC 6.3.0 (en ejecución cpp -traditional x-paste.c), obtengo:

# 1 "x-paste.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "x-paste.c"





extern void mine_3(char *x);

Con Clang de XCode 8.2.1, obtengo:

# 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);

Esos espacios lo estropean todo. Observo que ambos preprocesadores son correctos; diferentes preprocesadores preestándar exhibieron ambos comportamientos, lo que hizo que el pegado de tokens fuera un proceso extremadamente molesto y poco confiable al intentar portar código. El estándar con la ##notación lo simplifica radicalmente.

Puede que haya otras formas de hacer esto. Sin embargo, esto no funciona:

#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 genera:

# 1 "x-paste.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "x-paste.c"





extern void mine_VARIABLE(char *x);

Cerca, pero sin dados. YMMV, por supuesto, dependiendo del preprocesador preestándar que esté utilizando. Francamente, si tiene un preprocesador que no coopera, probablemente sería más sencillo utilizar un preprocesador C estándar en lugar del preestándar (generalmente hay una manera de configurar el compilador apropiadamente) que pasar mucho tiempo tratando de encontrar una manera de hacer el trabajo.

Jonathan Leffler avatar Sep 29 '2009 00:09 Jonathan Leffler

Usar:

#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);

Sinceramente, no querrás saber por qué funciona esto. Si sabes por qué funciona, te convertirás en ese tipo de trabajo que sabe este tipo de cosas y todos vendrán a hacerte preguntas. =)

Stephen Canon avatar Sep 29 '2009 00:09 Stephen Canon