Mientras trabajaba en un gran proyecto lleno de trucos y hechicería con macros, me topé con un error en el que una macro no se expandía correctamente. La salida resultante fue " EXPAND(0)
", pero EXPAND
se definió como " #define EXPAND(X) X
", por lo que claramente la salida debería haber sido " 0
".
"No hay problema", pensé para mis adentros. "Probablemente sea un error tonto, hay algunas macros desagradables aquí, después de todo, muchos lugares pueden salir mal". Mientras pensaba eso, aislé las macros que se comportaban mal en su propio proyecto, unas 200 líneas, y comencé a trabajar en un MWE para identificar el problema. 200 líneas se convirtieron en 150, que a su vez se convirtieron en 100, luego en 20, 10... Para mi absoluta sorpresa, este fue mi MWE final:
#define EXPAND(X) X #define PARENTHESIS() () #define TEST() EXPAND(0) EXPAND(TEST PARENTHESIS()) // EXPAND(0)
4 lineas
Para colmo de males, casi cualquier modificación a las macros hará que funcionen correctamente:
#define EXPAND(X) X #define PARENTHESIS() () #define TEST() EXPAND(0) // Manually replaced PARENTHESIS() EXPAND(TEST ()) // 0
#define EXPAND(X) X #define PARENTHESIS() () #define TEST() EXPAND(0) // Manually replaced TEST() EXPAND(EXPAND(0)) // 0
// Set EXPAND to 0 instead of X #define EXPAND(X) 0 #define PARENTHESIS() () #define TEST() EXPAND(0) EXPAND(TEST PARENTHESIS()) // 0
Pero lo más importante, y lo más extraño, el siguiente código falla exactamente de la misma manera:
#define EXPAND(X) X #define PARENTHESIS() () #define TEST() EXPAND(0) EXPAND(EXPAND(EXPAND(EXPAND(TEST PARENTHESIS())))) // EXPAND(0)
Esto significa que el preprocesador es perfectamente capaz de expandir EXPAND
, pero por alguna razón, se niega rotundamente a expandirlo nuevamente en el último paso.
Ahora, cómo voy a resolver este problema en mi programa real no está ni aquí ni allá. Aunque una solución sería buena (es decir, una forma de expandir el token EXPAND(TEST PARENTHESIS())
a 0
), lo que más me interesa es: ¿por qué? ¿Por qué el preprocesador de C llegó a la conclusión de que " EXPAND(0)
" era la expansión correcta en el primer caso, pero no en los otros?
Aunque es fácil encontrar recursos sobre lo que hace el preprocesador C (y algo de magia que puedes hacer con él), todavía tengo que encontrar uno que explique cómo lo hace, y quiero aprovechar esta oportunidad para entender mejor cómo funciona el preprocesador hace su trabajo y qué reglas usa al expandir macros.
Entonces, a la luz de eso: ¿Cuál es el razonamiento detrás de la decisión del preprocesador de expandir la macro final a " EXPAND(0)
" en lugar de " 0
"?
Editar: después de leer la respuesta muy detallada, lógica y bien expresada de Chris Dodd, hice lo que cualquiera haría en la misma situación ... intente encontrar un contraejemplo :)
Lo que inventé fue este diferente de 4 líneas:
#define EXPAND(X) X #define GLUE(X,Y) XY #define MACRO() GLUE(A,B) EXPAND(GLUE(MACRO, ())) // GLUE(A,B)
Ahora, sabiendo el hecho de que el preprocesador C no está completo en Turing , no hay forma de que lo anterior se expanda a AB
. Si ese fuera el caso, GLUE
expandiría MACRO
y MACRO
expandiría GLUE
. Eso conduciría a la posibilidad de una recursividad ilimitada, lo que probablemente implicaría la Completitud de Turing para el Cpp. Lamentablemente para los magos del preprocesador, la macro anterior que no se expande es una garantía.
Que fallar no es realmente el problema, el verdadero problema es: ¿Dónde? ¿Dónde decidió el preprocesador detener la expansión?
Analizando los pasos:
EXPAND
y escanea en la lista de argumentos GLUE(MACRO, ())
para X
GLUE(MACRO, ())
como una macro:MACRO
y ()
como argumentosMACRO ()
GLUE
y escanea MACRO ()
en busca de macros, encontrando MACRO
GLUE(A,B)
GLUE(A,B)
en busca de macros y encuentra GLUE
. Sin embargo, se suprime, por lo que se va como está.X
después del paso 2 es GLUE(A,B)
(observe que, dado que no estamos en el paso 4 de GLUE
, en teoría, ya no se suprime)GLUE(A,B)
EXPAND
y escanea GLUE(A,B)
en busca de más macros, encontrando GLUE
( uuh )A
y B
para los argumentos ( oh no )AB
( bueno... )AB
en busca de macros, pero no encuentra nadaAB
Cuál sería nuestro sueño. Lamentablemente, la macro se expande a GLUE(A,B)
.
Así que nuestra pregunta es: ¿Por qué?
A los efectos de esta situación, hay tres pasos relevantes en el reemplazo de macros:
En EXPAND(TEST PARENTHESIS())
:
EXPAND
, TEST PARENTHESIS()
:TEST
no va seguido de paréntesis, por lo que no se interpreta como una invocación de macro.PARENTHESIS()
es una invocación de macro, por lo que se realizan los tres pasos: Los argumentos están vacíos, por lo que no hay procesamiento para ellos. Luego PARENTHESIS()
se reemplaza por ()
. Luego ()
se vuelve a escanear y no se encuentran macros.EXPAND(TEST ())
. ( TEST ()
no se vuelve a escanear porque no fue el resultado de ningún reemplazo de macro).EXPAND(TEST ())
se reemplaza por TEST ()
.TEST ()
se vuelve a escanear mientras se suprime EXPAND
:TEST ()
se reemplaza por EXPAND(0)
.EXPAND(0)
, pero se suprime EXPAND
. En EXPAND(TEST ())
:
EXPAND
:TEST
están vacíos, por lo que no hay procesamiento.TEST ()
se reemplaza por EXPAND(0)
.EXPAND(0)
se reemplaza por 0
.EXPAND(TEST ())
se ha convertido en EXPAND(0)
y EXPAND(0)
se reemplaza por 0
.0
se vuelve a escanear en busca de más macros, pero no hay ninguna.Los otros ejemplos en la pregunta siguen de manera similar. Se reduce a:
TEST PARENTHESIS()
, la falta de paréntesis después de TEST
da como resultado que no se expanda mientras se procesan argumentos para una invocación de macro adjunta.PARENTHESIS
, pero esto es después de escanear TEST
y no se vuelve a escanear durante el procesamiento del argumento.TEST
y luego se reemplaza, pero, en este momento, se suprime el nombre de la macro adjunta.