while sirve sentencia que programacion para lenguaje else ejemplos c++ c c-preprocessor c++-faq

c++ - sirve - lenguaje c if else ejemplos



¿Por qué usar sentencias do-while y if-else aparentemente sin sentido en macros? (10)

En muchas macros de C / C ++ estoy viendo el código de la macro envuelto en lo que parece ser un bucle do while 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 do while está haciendo do while está haciendo. ¿Por qué no escribir esto sin él?

#define FOO(X) f(X); g(X)


Explicación

do {} while (0) y if (1) {} else es para asegurarse de que la macro se expande a solo 1 instrucción. De otra manera:

if (something) FOO(X);

se expandiría a:

if (something) f(X); g(X);

Y g(X) se ejecutaría fuera de la instrucción de control if . Esto se evita al usar do {} while (0) y if (1) {} else .

Mejor alternativa

Con una expresión de la declaración GNU (que no es parte del estándar C), tiene una mejor manera que do {} while (0) y if (1) {} else para resolver esto, simplemente usando ({}) :

#define FOO(X) ({f(X); g(X);})

Y esta sintaxis es compatible con los valores de retorno (tenga en cuenta que do {} while (0) no lo es), como en:

return FOO("X");


@ jfm3 - Tienes una buena respuesta a la pregunta. También es posible que desee agregar que el lenguaje macro también evita el comportamiento no deseado posiblemente más peligroso (porque no hay error) con simples declaraciones "si":

#define FOO(x) f(x); g(x) if (test) FOO( baz);

se expande a:

if (test) f(baz); g(baz);

que es sintácticamente correcto, por lo que no hay un error de compilación, pero tiene la consecuencia probablemente no intencionada de que g () siempre será llamado.


El do ... while y if ... else están ahí para hacer que un punto y coma después de la macro siempre signifique lo mismo. Digamos que tuviste algo como tu segunda macro.

#define BAR(X) f(x); g(x)

Ahora si fueras a usar BAR(X); en una declaración if ... else , donde los cuerpos de la declaración if no estaban envueltos entre corchetes, obtendría una mala sorpresa.

if (corge) BAR(corge); else gralt();

El código anterior se expandiría en

if (corge) f(corge); g(corge); else gralt();

lo cual es sintácticamente incorrecto, ya que la otra cosa ya no está asociada con el if. No ayuda encerrar las cosas en llaves dentro de la macro, porque un punto y coma después de las llaves es sintácticamente incorrecto.

if (corge) {f(corge); g(corge);}; else gralt();

Hay dos formas de solucionar el problema. El primero es usar una coma para secuenciar las sentencias dentro de la macro sin quitarle su capacidad de actuar como una expresión.

#define BAR(X) f(X), g(X)

La versión anterior de la barra BAR expande el código anterior a lo que sigue, que es sintácticamente correcto.

if (corge) f(corge), g(corge); else gralt();

Esto no funciona si en lugar de f(X) tiene 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 como do ... while que la macro es una sola declaración que toma un punto y coma sin confusión.

#define BAR(X) do { / int i = f(X); / if (i > 4) g(i); / } while (0)

No tienes que usar do ... while , podrías cocinar algo con if ... else también, aunque cuando if ... else expande dentro de if ... else conduce a un " else else" ", lo que podría hacer que un problema existente de otro tipo sea aún más difícil de encontrar, como en el siguiente código.

if (corge) if (1) { f(corge); g(corge); } else; else gralt();

El punto es usar el punto y coma en contextos donde un punto y coma colgado es erróneo. Por supuesto, podría (y probablemente debería) argumentarse en este punto que sería mejor declarar BAR como una función real, no como una macro.

En resumen, el do ... while está ahí para solucionar las deficiencias del preprocesador C. Cuando esas guías de estilo C le piden que retire el preprocesador C, este es el tipo de cosas que les preocupa.


He encontrado que este truco es muy útil en situaciones en las que tiene que procesar secuencialmente un valor particular. En cada nivel de procesamiento, si se produce algún error o condición no válida, puede evitar el procesamiento adicional y romper antes. p.ej

#define CALL_AND_RETURN(x) if ( x() == false) break; do { CALL_AND_RETURN(process_first); CALL_AND_RETURN(process_second); CALL_AND_RETURN(process_third); //(simply add other calls here) } while (0);


La biblioteca del preprocesador P99 de Jens Gustedt (sí, ¡el hecho de que exista algo así también me asombró!) Mejora la construcción if(1) { ... } else de una manera pequeña pero significativa al definir lo siguiente:

#define P99_NOP ((void)0) #define P99_PREFER(...) if (1) { __VA_ARGS__ } else #define P99_BLOCK(...) P99_PREFER(__VA_ARGS__) P99_NOP

La razón para esto es que, a diferencia de do { ... } while(0) construye, break y continue trabajando dentro del bloque dado, pero ((void)0) crea un error de sintaxis si el punto y coma se omite después de Llamada macro, que de otro modo saltearía el siguiente bloque. (En realidad no hay un problema de "otra cosa que cuelga" aquí, ya que la else enlaza con el if más cercano, que es el que está en la macro).

Si está interesado en el tipo de cosas que se pueden hacer de forma más o menos segura con el preprocesador de C, consulte esa biblioteca.


Las 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 la macro a comportarse como código genuino

El código normal suele terminar con un punto y coma. Si el usuario ve el código que no necesita uno ...

doSomething(1) ; DO_SOMETHING_ELSE(2) // <== Hey? What''s this? doSomethingElseAgain(3) ;

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 quizás deba reemplazar la macro con una función genuina (quizás en línea). Así que la macro realmente debería comportarse como una.

Así que 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 sentencia if, esto será problemático:

if(bIsOk) MY_MACRO(42) ;

Esta macro podría ser expandida como:

#define MY_MACRO(x) f(x) ; g(x) if(bIsOk) f(42) ; g(42) ; // was MY_MACRO(42) ;

La función g se ejecutará independientemente del valor de bIsOk .

Esto significa que debemos tener que agregar un alcance a la macro:

#define MY_MACRO(x) { f(x) ; g(x) ; } if(bIsOk) { f(42) ; g(42) ; } ; // was MY_MACRO(42) ;

Producir un código válido 2

Si la macro es algo como:

#define MY_MACRO(x) int i = x + 1 ; f(i) ;

Podríamos tener otro problema en el siguiente código:

void doSomething() { int i = 25 ; MY_MACRO(32) ; }

Porque se expandiría como:

void doSomething() { int i = 25 ; int i = 32 + 1 ; f(i) ; ; // was MY_MACRO(32) ; }

Este código no compilará, por supuesto. Entonces, una vez más, la solución está utilizando un alcance:

#define MY_MACRO(x) { int i = x + 1 ; f(i) ; } void doSomething() { int i = 25 ; { int i = 32 + 1 ; f(i) ; } ; // was MY_MACRO(32) ; }

El código se comporta correctamente de nuevo.

¿Combinando los efectos de punto y coma?

Hay un lenguaje C / C ++ que produce este efecto: el bucle do / while:

do { // code } while(false) ;

Do / while puede crear un alcance, por lo tanto, encapsula el código de la macro, y necesita un punto y coma al final, expandiéndose así al código que necesita uno.

La bonificación?

El compilador de C ++ optimizará el bucle 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:

#define MY_MACRO(x) / do / { / const int i = x + 1 ; / f(i) ; g(i) ; / } / while(false) void doSomething(bool bIsOk) { int i = 25 ; if(bIsOk) MY_MACRO(42) ; // Etc. }

se expandirá correctamente como

void doSomething(bool bIsOk) { int i = 25 ; if(bIsOk) do { const int i = 42 + 1 ; // was MY_MACRO(42) ; f(i) ; g(i) ; } while(false) ; // Etc. }

y luego es compilado y optimizado como

void doSomething(bool bIsOk) { int i = 25 ; if(bIsOk) { f(43) ; g(43) ; } // Etc. }


Las respuestas anteriores explican el significado de estos constructos, pero hay una diferencia significativa entre los dos que no se mencionó. De hecho, hay una razón para preferir el do ... while que el if ... else construye.

El problema de la construcción if ... else es que no te obliga a poner el punto y coma. Como en este código:

FOO(1) printf("abc");

Aunque omitimos el punto y coma (por error), el código se expandirá a

if (1) { f(X); g(X); } else printf("abc");

y compilará silenciosamente (aunque algunos compiladores pueden emitir una advertencia para el código inalcanzable). Pero la sentencia printf nunca será ejecutada.

do ... while construct no tiene ese problema, ya que el único token válido después de while(0) es un punto y coma.


No creo que haya sido mencionado así que considera esto

while(i<100) FOO(i++);

sería traducido a

while(i<100) do { f(i++); g(i++); } while (0)

observe cómo i++ es evaluado dos veces por la macro. Esto puede llevar a algunos errores interesantes.


Por algunas razones no puedo comentar sobre la primera respuesta ...

Algunos de ustedes mostraron macros con variables locales, ¡pero nadie mencionó que no puede usar cualquier nombre en una macro! ¡Morderá al usuario algún día! ¿Por qué? Debido a que los argumentos de entrada se sustituyen en su plantilla de macro. Y en sus ejemplos de macros, ha usado el nombre variable más utilizado probablemente i .

Por ejemplo, cuando la siguiente macro

#define FOO(X) do { int i; for (i = 0; i < (X); ++i) do_something(i); } while (0)

Se utiliza en la siguiente función.

void some_func(void) { int i; for (i = 0; i < 10; ++i) FOO(i); }

la macro no utilizará la variable deseada i, que se declara al comienzo de some_func, pero 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!


Si bien se espera que los compiladores optimicen el do { ... } while(false); Bucles, hay otra solución que no requeriría esa construcción. La solución es usar el operador de coma:

#define FOO(X) (f(X),g(X))

o incluso más exóticamente:

#define FOO(X) g((f(X),(X)))

Si bien esto funcionará bien con instrucciones separadas, no funcionará con los casos en que las variables se construyen y se usan como parte de #define :

#define FOO(X) (int s=5,f((X)+s),g((X)+s))

Con esto uno se vería obligado a usar la construcción do / while.