En muchas macros C / C ++, veo el código de la macro envuelto en lo que parece un do while
bucle sin sentido . Aquí hay ejemplos.
#define FOO(X) do { f(X); g(X); } while (0)
#define FOO(X) if (1) { f(X); g(X); } else
No puedo ver lo que do while
está haciendo. ¿Por qué no escribir esto sin él?
#define FOO(X) f(X); g(X)
c++
c
c-preprocessor
c++-faq
jfm3
fuente
fuente
void
tipo al final ... like ((void) 0) .do while
construcción no es compatible con las declaraciones de retorno, por lo que laif (1) { ... } else ((void)0)
construcción tiene usos más compatibles en el Estándar C. Y en GNU C, preferirá la construcción descrita en mi respuesta.Respuestas:
Los
do ... while
yif ... else
están ahí para que el punto y coma después de la macro siempre signifique lo mismo. Digamos que tenías algo así como tu segunda macro.Ahora, si fuera a usar
BAR(X);
en unaif ... else
declaración, donde los cuerpos de la declaración if no estuvieran envueltos entre llaves, obtendría una mala sorpresa.El código anterior se expandiría en
que es sintácticamente incorrecto, ya que lo demás ya no está asociado con el if. No ayuda envolver cosas en llaves dentro de la macro, porque un punto y coma después de las llaves es sintácticamente incorrecto.
Hay dos formas de solucionar el problema. El primero es usar una coma para secuenciar declaraciones dentro de la macro sin despojarla de su capacidad para actuar como una expresión.
La versión anterior de la barra
BAR
expande el código anterior a lo que sigue, que es sintácticamente correcto.Esto no funciona si en lugar de
f(X)
tener un cuerpo de código más complicado que necesita ir en su propio bloque, por ejemplo, para declarar variables locales. En el caso más general, la solución es usar algo comodo ... while
hacer que la macro sea una declaración única que toma un punto y coma sin confusión.No tiene que usarlo
do ... while
, también podría cocinar algoif ... else
, aunque cuando seif ... else
expande dentro de unif ... else
conduce a un " colgar otro ", lo que podría hacer que un problema de colgar existente sea aún más difícil de encontrar, como en el siguiente código .El punto es usar el punto y coma en contextos donde un punto y coma colgante es erróneo. Por supuesto, podría (y probablemente debería) argumentarse en este punto que sería mejor declararlo
BAR
como una función real, no como una macro.En resumen,
do ... while
está ahí para solucionar las deficiencias del preprocesador C. Cuando esas guías de estilo C le dicen que descarte el preprocesador C, este es el tipo de cosas que les preocupan.fuente
#define BAR(X) (f(X), g(X))
contrario, la precedencia del operador puede estropear la semántica.if
declaraciones, etc., en nuestro código usen llaves, entonces envolver macros como esta es una manera simple de evitar problemas.if(1) {...} else void(0)
formulario es más seguro quedo {...} while(0)
para las macros cuyos parámetros son código que se incluye en la expansión de macro, ya que no altera el comportamiento de la ruptura o continúa con las palabras clave. Por ejemplo:for (int i = 0; i < max; ++i) { MYMACRO( SomeFunc(i)==true, {break;} ) }
provoca un comportamiento inesperado cuandoMYMACRO
se define como#define MYMACRO(X, CODE) do { if (X) { cout << #X << endl; {CODE}; } } while (0)
porque la interrupción afecta el ciclo while de la macro en lugar del ciclo for en el sitio de llamada de macro.void(0)
era un error tipográfico, quise decir(void)0
. Y creo que esto no resuelve el "colgando otro" problema: aviso no hay punto y coma después de la(void)0
. Un otro colgante en ese caso (por ejemploif (cond) if (1) foo() else (void)0 else { /* dangling else body */ }
) desencadena un error de compilación. Aquí hay un ejemplo en vivo que lo demuestraLas macros son piezas de texto copiadas / pegadas que el preprocesador colocará en el código original; El autor de la macro espera que el reemplazo produzca un código válido.
Hay tres buenos "consejos" para tener éxito en eso:
Ayuda a que la macro se comporte como un código genuino
El código normal generalmente termina con un punto y coma. Si el usuario ve el código que no necesita uno ...
Esto significa que el usuario espera que el compilador produzca un error si el punto y coma está ausente.
Pero la verdadera buena razón es que, en algún momento, el autor de la macro tal vez necesite reemplazar la macro con una función genuina (tal vez en línea). Entonces la macro realmente debería comportarse como tal.
Entonces deberíamos tener una macro que necesite punto y coma.
Producir un código válido
Como se muestra en la respuesta de jfm3, a veces la macro contiene más de una instrucción. Y si la macro se usa dentro de una declaración if, esto será problemático:
Esta macro podría expandirse como:
La
g
función se ejecutará independientemente del valor debIsOk
.Esto significa que debemos agregar un ámbito a la macro:
Producir un código válido 2
Si la macro es algo como:
Podríamos tener otro problema en el siguiente código:
Porque se expandiría como:
Este código no se compilará, por supuesto. Entonces, nuevamente, la solución está usando un alcance:
El código se comporta correctamente nuevamente.
¿Combina los efectos de punto y coma + alcance?
Hay un modismo C / C ++ que produce este efecto: el bucle do / while:
El do / while puede crear un ámbito, encapsulando así el código de la macro, y necesita un punto y coma al final, expandiéndose así en el código que lo necesita.
El bono?
El compilador de C ++ optimizará el ciclo do / while, ya que el hecho de que su condición posterior sea falsa se conoce en el momento de la compilación. Esto significa que una macro como:
se expandirá correctamente como
y luego se compila y optimiza como
fuente
void doSomething() { int i = 25 ; { int i = x + 1 ; f(i) ; } ; // was MY_MACRO(32) ; }
no es la expansión correcta; elx
de la expansión debería ser 32. Una cuestión más compleja es lo que es la expansión deMY_MACRO(i+7)
. Y otra es la expansión deMY_MACRO(0x07 << 6)
. Hay muchas cosas buenas, pero hay algunas i sin marcar y t sin cruzar.\_\_LINE\_\_
lo escribes se representa como __LINE__. En mi humilde opinión, es mejor usar el formato de código para el código; por ejemplo,__LINE__
(que no requiere ningún manejo especial). PD: No sé si esto fue cierto en 2012; Han hecho bastantes mejoras en el motor desde entonces.inline
funciones en línea (como lo permite el estándar)@ jfm3: tiene una buena respuesta a la pregunta. Es posible que también desee agregar que el lenguaje idiomático también previene el comportamiento involuntario posiblemente más peligroso (porque no hay error) con simples declaraciones 'if':
se expande a:
que es sintácticamente correcto para que no haya un error del compilador, pero tiene la consecuencia probablemente no intencionada de que siempre se llamará a g ().
fuente
Las respuestas anteriores explican el significado de estos constructos, pero existe una diferencia significativa entre los dos que no se mencionó. De hecho, hay una razón para preferir el
do ... while
a laif ... else
construcción.El problema de la
if ... else
construcción es que no te obliga a poner el punto y coma. Como en este código:Aunque omitimos el punto y coma (por error), el código se expandirá a
y se compilará en silencio (aunque algunos compiladores pueden emitir una advertencia para el código inalcanzable). Pero la
printf
declaración nunca se ejecutará.do ... while
La construcción no tiene ese problema, ya que el único token válido después delwhile(0)
es un punto y coma.fuente
FOO(1),x++;
lo que nuevamente nos dará un falso positivo. Solo usedo ... while
y eso es todo.do ... while (0)
es preferible, pero tiene una desventaja: Abreak
ocontinue
controlará eldo ... while (0)
ciclo, no el ciclo que contiene la invocación de macro. Entonces elif
truco todavía tiene valor.break
o unacontinue
que se vería dentro deldo {...} while(0)
pseudo bucle de macros . Incluso en el parámetro macro haría un error de sintaxis.do { ... } while(0)
lugar deif whatever
construir, es la naturaleza idiomática de la misma. Lado {...} while(0)
construcción es generalizada, bien conocida y utilizada por muchos programadores. Su justificación y documentación se conocen fácilmente. No es así para laif
construcción. Por lo tanto, se requiere menos esfuerzo para asimilar cuando se hace una revisión de código.#define CHECK(call, onerr) if (0 != (call)) { onerr } else (void)0
. Podría usarse comoCHECK(system("foo"), break;);
, dondebreak;
está destinado a referirse al bucle que encierra laCHECK()
invocación.Si bien se espera que los compiladores optimicen los
do { ... } while(false);
bucles, existe otra solución que no requeriría esa construcción. La solución es usar el operador de coma:o incluso más exóticamente:
Si bien esto funcionará bien con instrucciones separadas, no funcionará con casos en los que las variables se construyan y utilicen como parte de
#define
:Con esto, se vería obligado a usar la construcción do / while.
fuente
Peterson's Algorithm
.La biblioteca de preprocesador P99 de Jens Gustedt (sí, ¡el hecho de que tal cosa exista también me dejó boquiabierto !) Mejora la
if(1) { ... } else
construcción de una manera pequeña pero significativa al definir lo siguiente:La razón de esto es que, a diferencia de la
do { ... } while(0)
construcción,break
ycontinue
todavía trabajan en el interior del bloque dado, pero el((void)0)
crea un error de sintaxis si se omite el punto y coma después de la llamada a la macro, que de otro modo saltarse el siguiente bloque. (No hay realmente un problema de "colgar más" aquí, ya que seelse
une al más cercanoif
, que es el de la macro).Si está interesado en los tipos de cosas que se pueden hacer de manera más o menos segura con el preprocesador C, consulte esa biblioteca.
fuente
break
(ocontinue
) dentro de una macro para controlar un ciclo que comenzó / terminó afuera, eso es simplemente un mal estilo y oculta posibles puntos de salida.else ((void)0)
es que alguien podría estar escribiendoYOUR_MACRO(), f();
y será sintácticamente válido, pero nunca llamef()
. Condo
while
es un error de sintaxis.else do; while (0)
?Por algunas razones, no puedo comentar sobre la primera respuesta ...
¡Algunos de ustedes mostraron macros con variables locales, pero nadie mencionó que no pueden usar cualquier nombre en una macro! ¡Morderá al usuario algún día! ¿Por qué? Porque los argumentos de entrada se sustituyen en su plantilla de macro. Y en sus ejemplos de macros que has utilizar el nombre variabled probablemente más utilizado i .
Por ejemplo cuando la siguiente macro
se usa en la siguiente función
la macro no usará la variable deseada i, que se declara al comienzo de some_func, sino la variable local, que se declara en el bucle do ... while de la macro.
Por lo tanto, ¡nunca use nombres de variables comunes en una macro!
fuente
int __i;
.mylib_internal___i
o similares.Explicación
do {} while (0)
yif (1) {} else
deben asegurarse de que la macro se expanda a solo 1 instrucción. De otra manera:se expandiría a:
Y
g(X)
sería ejecutado fuera de laif
declaración de control. Esto se evita al usardo {} while (0)
yif (1) {} else
.Mejor alternativa
Con una expresión de instrucción GNU (que no forma parte del estándar C), tiene una mejor manera que
do {} while (0)
yif (1) {} else
para resolver esto, simplemente usando({})
:Y esta sintaxis es compatible con los valores de retorno (tenga en cuenta que
do {} while (0)
no lo es), como en:fuente
No creo que se haya mencionado, así que considera esto
se traduciría a
observe cómo
i++
la macro la evalúa dos veces. Esto puede conducir a algunos errores interesantes.fuente
do { int macroname_i = (i); f(macroname_i); g(macroname_i); } while (/* CONSTCOND */ 0)