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

11 minutos de lectura

avatar de usuario
JJ.

Estoy tratando de escribir un programa donde los nombres de algunas funciones dependen del valor de una determinada variable de macro 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 incorrecta de hacerlo. Afortunadamente, el número de diferentes valores posibles para VARIABLE es pequeño, así que simplemente puedo hacer un #if VARIABLE == n y enumere todos los casos por separado, pero ¿hay una forma inteligente de hacerlo?

  • ¿Está seguro de que no desea utilizar punteros de función en su lugar?

    – György Andrasek

    29 de septiembre de 2009 a las 0:26

  • @Jurily: los punteros de función funcionan en tiempo de ejecución, el preprocesador funciona en (antes) del tiempo de compilación. Hay una diferencia, incluso si ambos pueden usarse para la misma tarea.

    – Chris Lutz

    29 de septiembre de 2009 a las 0:29

  • El punto es que se usa en una biblioteca de geometría computacional rápida… que está programada para una determinada dimensión. Sin embargo, a veces alguien querría poder usarlo con algunas dimensiones diferentes (por ejemplo, 2 y 3) y, por lo tanto, se necesitaría una forma fácil de generar código con función dependiente de la dimensión y nombres de tipo. Además, el código está escrito en ANSI C, por lo que las cosas originales de C ++ con plantillas y especialización no se aplican aquí.

    – JJ.

    29 de septiembre de 2009 a las 3:33

  • Votar para reabrir porque esta pregunta es específica sobre la expansión macro recursiva y stackoverflow.com/questions/216875/using-in-macros es un genérico “para qué sirve”. El título de esta pregunta debería ser más preciso.

    – Ciro Santilli Путлер Капут 六四事

    31 de mayo de 2015 a las 23:26


  • Ojalá este ejemplo se hubiera minimizado: lo mismo sucede en #define A 0 \n #define M a ## A: tener dos ## no es la clave.

    – Ciro Santilli Путлер Капут 六四事

    21 de junio de 2015 a las 9:49


avatar de usuario
jonathan leffler

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 requiere que funcione; tiendes a descubrir que también necesitas el truco equivalente con el operador de cadena.

La sección 6.10.3 del estándar C99 cubre el ‘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, tiene lugar la sustitución de argumentos. Un parámetro en la lista de reemplazo, a menos que esté precedido por un # o ## 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 por completo como si formaran el resto del archivo de preprocesamiento; no hay otros tokens de preprocesamiento disponibles.

en la invocacion 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 la macro EVALUADOR, y los argumentos se aíslan como ‘mío’ y ‘VARIABLE’; el último se expande completamente a ‘3’ y se sustituye en la cadena de reemplazo:

PASTER(mine, 3)

La operación de esto está cubierta 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 es inmediatamente precedido o seguido por un ## token de preprocesamiento, el parámetro se reemplaza por la secuencia del token de preprocesamiento del argumento correspondiente; […]

Tanto para las invocaciones de macros de tipo objeto como de función, antes de que se vuelva a examinar la lista de reemplazo en busca de más nombres de macro para reemplazar, cada instancia de un ## el 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 eliminando la ## tokens y concatenar los tokens en cada lado combina ‘mío’ con ‘_’ y ‘3’ para producir:

mine_3

Este es el resultado deseado.


Si observamos la pregunta original, el código fue (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 expandido.
Siguiendo las reglas de 6.10.3.3, encontramos:

mine ## _ ## VARIABLE

que, cuando el ## 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 forma 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 función que funciona de manera confiable, mientras que hubo diferentes implementaciones para los preprocesadores preestándar. Un requisito es que cuando el preprocesador reemplaza un comentario, no genera un espacio como se requiere que lo haga el preprocesador ANSI. El preprocesador GCC (6.3.0) C cumple 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 terminas con mine_ 3 como el nombre, que no es sintácticamente válido, por supuesto. (Ahora, por favor, ¿puedo recuperar mi cabello?)

Con GCC 6.3.0 (ejecutando cpp -traditional x-paste.c), Yo 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 cuando se intentaba portar el código. El estándar con el ## 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, según el preprocesador preestándar que esté utilizando. Francamente, si está atascado con un preprocesador que no está cooperando, probablemente sería más sencillo hacer arreglos para usar un preprocesador C estándar en lugar del preestándar (generalmente hay una manera de configurar el compilador adecuadamente) que pasar mucho tiempo tratando de encontrar una manera de hacer el trabajo.

  • Sí, esto resuelve el problema. Conocía el truco con dos niveles de recursividad, tuve que jugar con la stringificación al menos una vez, pero no sabía cómo hacer esto.

    – JJ.

    29 de septiembre de 2009 a las 3:34

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

    – Robert Ruger

    24 de diciembre de 2016 a las 0:03

  • @RobertRüger: duplica la longitud de la respuesta, pero agregué información para cubrir cpp -traditional. Tenga en cuenta que no hay una respuesta definitiva, depende del preprocesador que tenga.

    –Jonathan Leffler

    24 de diciembre de 2016 a las 2:40

  • Muchas gracias por la respuesta. ¡Esto es totalmente genial! Mientras tanto, también encontré otra solución ligeramente diferente. Mira aquí. Sin embargo, también tiene el problema de que no funciona con clang. Afortunadamente, eso no es un problema para mi aplicación…

    – Robert Ruger

    26 de diciembre de 2016 a las 11:03

avatar de usuario
Esteban Canon

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

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

  • ¿Podría explicar por qué necesita dos niveles de direccionamiento indirecto? Tenía una respuesta con un nivel de redirección, pero eliminé la respuesta porque tuve que instalar C++ en mi Visual Studio y luego no funcionó.

    –Cade Roux

    29 de septiembre de 2009 a las 1:31

avatar de usuario
Ciro Santilli Путлер Капут 六四事

Explicación en inglés sencillo de la EVALUATOR patrón de dos pasos

No he entendido completamente cada palabra del estándar C, pero creo que este es un modelo de trabajo razonable sobre cómo funciona la solución que se muestra en la respuesta de Jonathan Leffler, explicada un poco más detalladamente. Avíseme si mi comprensión es incorrecta, con suerte con un ejemplo mínimo que rompa mi teoría.

Para nuestros propósitos, podemos pensar que la macro expansión ocurre en tres pasos:

  1. (preescaneado) Los argumentos de macro se reemplazan:
    • si son parte de la concatenación o la encadenación, se reemplazan exactamente como la cadena dada en la llamada de macro, sin expandirse
    • de lo contrario, primero se expanden por completo y solo luego se reemplazan
  2. La encadenación y la concatenación ocurren
  3. Todas las macros definidas se expanden

Ejemplo paso a paso sin indirecta

C Principal

#define CAT(x) pref_ ## x
#define Y a

CAT(Y)

y expandirlo con:

gcc -E main.c

obtenemos:

pref_Y

porque:

Paso 1: Y es un argumento macro de CAT.

x aparece en una stringificacion pref_ ## x. Por lo tanto, Y se pega tal cual sin expansión dando:

pref_ ## Y

Paso 2: ocurre la concatenación y nos queda:

pref_Y

Paso 3: ocurre cualquier otro reemplazo de macro. Pero pref_Y no hay ninguna macro conocida, por lo que se deja sola.

Podemos confirmar esta teoría agregando una definición a pref_Y:

#define CAT(x) pref_ ## x
#define Y a
#define pref_Y asdf

CAT(Y)

y ahora el resultado seria:

asdf

porque en el paso 3 anterior pref_Y ahora se define como una macro y, por lo tanto, se expande.

Ejemplo paso a paso con indirección

Sin embargo, si usamos el patrón de dos pasos:

#define CAT2(x) pref_ ## x
#define CAT(x) CAT2(x)
#define Y a

CAT(Y)

obtenemos:

pref_a

Paso 1: CAT se evalúa.

CAT(x) Se define como CAT2(x)entonces argumento x de CAT en la definición no aparece en una encadenación: la encadenación solo ocurre después CAT2 se expande, lo que no se ve en este paso.

Por lo tanto, Y está completamente expandido antes de ser reemplazado, pasando por los pasos 1, 2 y 3, que omitimos aquí porque trivialmente se expande a a. Así que ponemos a en CAT2(x) donación:

CAT2(a)

Paso 2: no hay que hacer stringification

Paso 3: expande todas las macros existentes. Tenemos la macro CAT2(a) y así continuamos expandiendo eso.

Paso 3.1: el argumento x de CAT2 aparece en una stringificacion pref_ ## x. Por lo tanto, pegue la cadena de entrada a tal cual, dando:

pref_ ## a

Paso 3.2: encadenar:

pref_a

Paso 3: expanda cualquier macro adicional. pref_a no es ninguna macro, así que hemos terminado.

Documentación preescaneada del argumento GCC

También vale la pena leer la documentación de GCC sobre el tema: https://gcc.gnu.org/onlinedocs/cpp/Argument-Prescan.html

Bonificación: cómo esas reglas evitan que las llamadas anidadas se vuelvan infinitas

Ahora considera:

#define f(x) (x + 1)

f(f(a))

que se expande a:

((a + 1) + 1)

en lugar de ir infinito.

Vamos a desglosarlo:

Paso 1: el exterior f se llama con argumento x = f(a).

En la definición de fel argumento x no es parte de una concatenación en la definición (x + 1) de f. Por lo tanto, primero se expande por completo antes de ser reemplazado.

Paso 1.1.: ampliamos completamente el argumento x = f(1) de acuerdo con los pasos 1, 2 y 3, dando x = (a + 1).

Ahora de vuelta en el Paso 1, lo tomamos completamente expandido x argumento de igualdad (a + 1)y ponerlo dentro de la definición de f donación:

((a + 1) + 1)

Pasos 2 y 3: no pasa mucho, porque no tenemos cadenas ni más macros para expandir.

¿Ha sido útil esta solución?