Creando macro C con ## y __LINE__ (concatenación de tokens con macro de posicionamiento)

107

Quiero crear una macro en C que cree una función con un nombre basado en el número de línea. Pensé que podría hacer algo como (la función real tendría declaraciones entre llaves):

#define UNIQUE static void Unique_##__LINE__(void) {}

Que esperaba que se expandiera a algo como:

static void Unique_23(void) {}

Eso no funciona. Con la concatenación de tokens, las macros de posicionamiento se tratan literalmente y terminan expandiéndose a:

static void Unique___LINE__(void) {}

¿Es posible hacerlo?

(Sí, hay una razón real por la que quiero hacer esto, sin importar cuán inútil parezca).

DD.
fuente
Creo que puede hacer que esto funcione con una macro expansión indirecta .
Ben Stiglitz
4
posible duplicado de ¿Cómo concatenar dos veces con el preprocesador de C y expandir una macro como en "arg ## _ ## MACRO"? Lo mismo ocurre con cualquier macro además __LINE__(aunque ese es un caso de uso común.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Respuestas:

176

El problema es que cuando tiene un reemplazo de macro, el preprocesador solo expandirá las macros de forma recursiva si no se le aplica el operador de encadenamiento #ni el de pegar token ##. Por lo tanto, debe usar algunas capas adicionales de indirección, puede usar el operador de pegado de tokens con un argumento expandido de forma recursiva:

#define TOKENPASTE(x, y) x ## y
#define TOKENPASTE2(x, y) TOKENPASTE(x, y)
#define UNIQUE static void TOKENPASTE2(Unique_, __LINE__)(void) {}

A continuación, __LINE__obtiene expandió al número de línea durante la expansión UNIQUE(ya que no está involucrado, ya sea con #o ##), y luego el símbolo pegar sucede durante la expansión de TOKENPASTE.

También debe tenerse en cuenta que también existe la __COUNTER__macro, que se expande a un nuevo entero cada vez que se evalúa, en caso de que necesite tener múltiples instancias de la UNIQUEmacro en la misma línea. Nota: __COUNTER__es compatible con MS Visual Studio, GCC (desde V4.3) y Clang, pero no es estándar C.

Adam Rosenfield
fuente
3
Me temo que eso no funciona con GNU cpp. TOKENPASTE usa LINE como literal. TOKENPASTE (Unique_, LINE ) se expande a Unique___LINE__
DD.
3
@DD: D'oh, arreglado ahora. Necesita 2 capas de indirección, no 1.
Adam Rosenfield
La __COUNTER__macro no me funcionó en gcc; aunque el __LINE__uno funcionó como se anuncia.
Tyler
2
Un poco de información adicional para cualquiera que pruebe COUNTER , según msdn.microsoft.com/en-us/library/b0084kay(v=vs.80).aspx es una macro específica de Microsoft.
Elva
3
¿Alguna explicación de por qué necesita 2 niveles de indirección? Lo probé con solo uno, sin # y ##, y eso no lo expande en VS2017. Aparentemente, lo mismo ocurre con GCC. Pero si agrega un segundo nivel de indirección, entonces se expande. ¿Magia?
Gabe Halsmer
-2

GCC no requiere "envolver" (o darse cuenta) a menos que el resultado necesite ser "encadenado". Gcc tiene características, pero TODAS se pueden hacer con la versión 1 de C simple (y algunos argumentan que Berkeley 4.3 C es mucho más rápido que vale la pena aprender a usarlo).

** Clang (llvm) NO HACE EL ESPACIO EN BLANCO CORRECTAMENTE para la expansión macro; agrega espacios en blanco (que ciertamente destruye el resultado como un identificador C para un procesamiento previo adicional) **, clang simplemente no hace # o * expansión macro como un preprocesador de C durante décadas. El ejemplo principal es compilar X11, la macro "Concat3" está rota, su resultado ahora es Identificador C MISNAMED, que por supuesto no se puede construir. y estoy empezando a pensar que los fallos de construcción son su profesión.

Creo que la respuesta aquí es "la nueva C que rompe los estándares es mala C", estos hacks siempre eligen (aplastar los espacios de nombres), cambian los valores predeterminados sin ninguna razón, pero en realidad no "mejoran C" (excepto por lo que ellos dicen: que yo digamos que es un artilugio hecho para explicar por qué se salen con la suya con todas las roturas de las que nadie los ha hecho responsables).


No es un problema que los preprocesadores de C anteriores no admitieran UNIq_ () __ porque admitían #pragma, lo que permite que "la piratería de la marca del compilador en el código se marque como piratería" y también funcione igual de bien SIN afectar los estándares: simplemente como cambiar los valores predeterminados es una rotura inútil de wonton, y al igual que cambiar lo que hace una función mientras se usa el mismo nombre (espacio de nombres) es ... malware en mi opinión

nadie se alió
fuente