2011-04-12 24 views
14

¿Por qué M (0) y N (0) tienen resultados diferentes?Preprocesador C, macros recursivas

#define CAT_I(a, b) a ## b 
#define CAT(a, b) CAT_I(a, b) 

#define M_0 CAT(x, y) 
#define M_1 whatever_else 
#define M(a) CAT(M_, a) 
M(0);  // expands to CAT(x, y) 

#define N_0() CAT(x, y) 
#define N_1() whatever_else 
#define N(a) CAT(N_, a)() 
N(0);  // expands to xy 
+0

Uhhh ... ¿qué es exactamente lo que está tratando de lograr aquí ... y con qué propósito? – t0mm13b

+15

Realmente no quiero lograr nada, solo noté esto mientras trabajaba en algo, y tengo curiosidad sobre las razones. Me molesta cuando no entiendo algo :). – imre

Respuesta

17

De hecho , depende de su interpretación del estándar de idioma. Por ejemplo, bajo mCPP, una implementación del preprocesador que se ajusta estrictamente al texto de la lengua estándar, los segundos rendimientos CAT(x, y); así como [nuevas líneas adicionales se han eliminado a partir del resultado]:

C:\dev>mcpp -W0 stubby.cpp 
#line 1 "C:/dev/stubby.cpp" 
     CAT(x, y) ; 
     CAT(x, y) ; 
C:\dev> 

Hay a known inconsistency en el C++ Especificación del lenguaje (la misma incoherencia está presente en la especificación C, aunque no sé dónde está la lista de defectos para C). La especificación establece que el CAT(x, y) final no se debe reemplazar en macro. La intención puede haber sido que debería ser macro-reemplazado.

citar el informe de defectos vinculados:

Ya en la década de 1980 se entiende por varios WG14 personas que no eran pequeñas diferencias entre la verborrea "no sustitución" y los intentos de producir pseudo-código.

La decisión del comité fue que ningún programa realista "en la naturaleza" se aventuraría en esta área, y tratar de reducir las incertidumbres no justifica el riesgo de cambiar el estado de cumplimiento de las implementaciones o programas.


Entonces, ¿por qué obtenemos un comportamiento diferente para M(0)N(0) que para la mayoría de las implementaciones del preprocesador comunes? En la sustitución de M, la segunda invocación de CAT consiste enteramente de tokens resultantes de la primera invocación de CAT:

M(0) 
CAT(M_, 0) 
CAT_I(M_, 0) 
M_0 
CAT(x, y) 

Si M_0 lugar se definió para ser sustituido por CAT(M, 0), la sustitución sería recurse infinitamente. La especificación del preprocesador prohíbe explícitamente esta sustitución "estrictamente recursiva" al detener la sustitución de macro, por lo que CAT(x, y) no se reemplaza macro.

Sin embargo, en la sustitución de N, la segunda invocación de CAT consiste sólo parcialmente de tokens resultantes de la primera invocación de CAT:

N(0) 
CAT(N_, 0)  () 
CAT_I(N_, 0) () 
N_0    () 
CAT(x, y) 
CAT_I(x, y) 
xy 

Aquí la segunda invocación de CAT se forma parcialmente a partir de tokens resultante de la primera invocación de CAT y parcialmente de otros tokens, a saber, el () de la lista de reemplazo de N. El reemplazo no es estrictamente recursivo y, por lo tanto, cuando se reemplaza la segunda invocación de CAT, no puede producir recursión infinita.

+0

Interesante ... Los preprocesadores en VC++ y el compilador Comeau en línea ambos expanden N (0) a "xy". – imre

+0

Además, ¿es de alguna manera posible evitar esta limitación de recursión y hacer que el último CAT evalúe? (¿Además de definir otra alternativa CAT?) – imre

+1

Tengo una memoria tenue en el sentido de que debido a que el '()' proporcionado a 'N_0' viene de fuera de cualquier macro expansión, eso cuenta como una * macro * expansión, por lo que el" azul paint "sale de' CAT() 'y se puede expandir una vez más. Entonces esto podría ser un error en mcpp. FWIW gcc está de acuerdo con Comeau y VC++. – zwol

0

Parece que hay algo que es posible que haya fallado en detectar, pero la macro tiene N(a) CAT(N_,a)(), mientras que M (a) se define como CAT(M_, a) Aviso los soportes adicionales de parámetros utilizados ....

+0

Lo sé. Y, en consecuencia, N_0 se define como una macro de estilo de función (0 argumentos). Y por alguna razón, eso parece marcar una diferencia en las evaluaciones recursivas, pero no sé exactamente por qué; esa es mi pregunta. – imre

4

Simplemente siga la secuencia:

1.)

M(0); // expands to CAT(x, y) TRUE 
CAT(M_, 0) 
CAT_I(M_, 0) 
M_0 
CAT(x, y) 

2.)

N(0); // expands to xy TRUE 
CAT(N_, 0)() 
CAT_I(N_, 0)() 
N_0() 
CAT(x, y) 
CAT_I(x, y) 
xy 

Sólo es necesario reemplazar de forma recursiva las macros.

Notas sobre el ## operador preprocesador: Dos argumentos pueden 'pegados' juntos usando ## operador preprocesador; esto permite que dos tokens se concatenen en el código preprocesado.

A diferencia de la expansión de macro estándar, la expansión de macro tradicional no tiene ninguna disposición para evitar la recursión. Si una macro similar a un objeto aparece sin comillas en su texto de reemplazo, se reemplazará nuevamente durante el pase de reescaneo, y así sucesivamente hasta el infinito. GCC detecta cuándo está expandiendo las macros recursivas, emite un mensaje de error y continúa después de la invocación de la macro problemática. (gcc online doc)

+1

Umm ... Todavía no lo entiendo. Ambas secuencias alcanzan el mismo CAT (x, y), entonces, ¿por qué detenerse allí en un caso pero no en el otro? – imre

+0

Creo que la recursión aquí depende de la interpretación de la norma, como dijo James McNellis. Buena pregunta imre. –

+1

@imre: En el caso de 'M (0)', la segunda invocación 'CAT (...)' resulta enteramente de la primera invocación 'CAT (...)', por lo tanto es una llamada estrictamente recursiva. En el caso de 'N (0)', la segunda invocación 'CAT (...)' resulta solo parcialmente de la primera invocación 'CAT (...)' y parcialmente de otros tokens que aparecen después de eso (el '() 'en la lista de reemplazo de' N'). Por lo tanto, no es completamente recursivo. –

Cuestiones relacionadas