Preprocesador estándar C
$ 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 impertinente es porque así es como el estándar requiere que funcione; tiende a encontrar que necesita el truco equivalente con el operador de encordado también.
La Sección 6.10.3 de la norma C99 cubre el 'reemplazo de macros' y 6.10.3.1 cubre la 'sustitución de argumentos'.
Una vez que se han identificado los argumentos para la invocación de una macro similar a una función, tiene lugar la sustitución de argumentos. Un parámetro de la lista de sustitución, a menos precedida por una #
o ##
preprocesamiento ficha o seguido de un ##
procesamiento previo de contadores (véase abajo), se sustituye por el argumento correspondiente después de todos macros contenidas en el mismo se han ampliado. Antes de ser sustituidos, los tokens de preprocesamiento de cada argumento se reemplazan completamente por macro 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 EVALUATOR, y los argumentos se aíslan como 'mío' y 'VARIABLE'; este último 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á precedido o seguido inmediatamente por un ##
token de preprocesamiento, el parámetro se reemplaza por la secuencia de tokens de preprocesamiento del argumento correspondiente; [...]
Para las invocaciones de macro tanto de objeto como de función, antes de volver a examinar la lista de reemplazo para reemplazar más nombres de macro, 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 x
seguido por ##
y también ##
seguido por y
; entonces tenemos:
mine ## _ ## 3
y eliminar las ##
fichas y concatenar las fichas en ambos lados combina 'mina' con '_' y '3' para obtener:
mine_3
Este es el resultado deseado.
Si miramos la pregunta original, el código fue (adaptado para usar 'mine' en lugar de 'some_function'):
#define VARIABLE 3
#define NAME(fun) fun ## _ ## VARIABLE
NAME(mine)
El argumento para NAME es claramente 'mío' y eso se expande completamente.
Siguiendo las reglas de 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, y tal vez no, depende del preprocesador. Una de las ventajas del preprocesador estándar es que tiene esta instalación que funciona de manera confiable, mientras que hubo diferentes implementaciones para preprocesadores pre-estándar. Un requisito es que cuando el preprocesador reemplaza un comentario, no genera un espacio como debe hacer 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_ 3
el nombre, que no es sintácticamente válido, por supuesto. (Ahora, por favor, ¿puedo tener mi cabello hacia atrás?)
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 pre-está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 simplifica radicalmente eso.
Puede haber 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 pre-estándar que esté usando. Francamente, si está atascado con un preprocesador que no está cooperando, probablemente sería más sencillo organizar el uso de un preprocesador C estándar en lugar del preprocesador (generalmente hay una manera de configurar el compilador de manera adecuada) que Pasar mucho tiempo tratando de encontrar una manera de hacer el trabajo.