En muchos de C/C++ macros estoy viendo el código de la macro envuelto en lo que parece un sin sentido do while bucle. Aquí están algunos ejemplos.

#define FOO(X) do { f(X); g(X); } while (0)
#define FOO(X) if (1) { f(X); g(X); } else

Yo no puedo ver lo que el do while está haciendo. ¿Por qué no escribir esto sin ella?

#define FOO(X) f(X); g(X)
Para el ejemplo con los demás, me gustaría añadir una expresión de void tipo al final… como ((void)0).
Recordatorio de que el do while construir no es compatible con el retorno de las declaraciones, por lo que el if (1) { ... } else ((void)0) construir tiene más usos compatibles en el Estándar de la C. Y en C de GNU, preferirá que la construcción se describe en mi respuesta.

OriginalEl autor jfm3 | 2008-09-30

11 Comentarios

  1. 738

    La do ... while y if ... else están ahí para hacer que una
    punto y coma después de la macro siempre significa lo mismo. Digamos que usted
    tenía algo así como su segunda macro.

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

    Ahora, si usted fuera a utilizar BAR(X); en un if ... else declaración, donde los cuerpos de la declaración si no se han ajustado en llaves, se obtendría una mala sorpresa.

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

    El código anterior podría expandirse en

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

    que es sintácticamente incorrecta, ya que el otro ya no está asociado con el si. No ayuda a envolver las cosas en las llaves dentro de la macro, debido a que un punto y coma después de las llaves es sintácticamente incorrecta.

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

    Hay dos formas de solucionar el problema. La primera es utilizar una coma para la secuencia de instrucciones dentro de la macro sin robarle de su capacidad para actuar como una expresión.

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

    La versión anterior de la barra de BAR expande el código anterior en 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 una más complicada del cuerpo de código que necesita para ir en su propio bloque, digamos, por ejemplo, para declarar variables locales. En el caso más general la solución es usar algo como do ... while a causa de la macro a ser una sola instrucción que se lleva un punto y coma sin confusión.

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

    Usted no tiene que usar do ... while, usted puede cocinar algo con if ... else así, aunque cuando if ... else se expande en el interior de un if ... else conduce a un «colgando más«, que se podría hacer una existente colgando otro problema 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 que para usar el punto y coma en contextos donde un colgando punto y coma es errónea. Por supuesto, podría (y debería) ser defendido en este punto que sería mejor declarar BAR como una función real, no una macro.

    En resumen, la do ... while está ahí para evitar las deficiencias de la C preprocesador. Cuando los C guías de estilo decirle a despedir el preprocesador de C, este es el tipo de cosa que les preocupa.

    Probablemente el mejor ejemplo de por qué usted NO DEBERÍA estar haciendo esto …
    No es este un argumento de peso para siempre utilice llaves en si, y para las declaraciones? Si usted hace un punto de siempre hacer esto (como se requiere para la MISRA-C, por ejemplo), el problema descrito anteriormente desaparece.
    ¿Por qué es que me siento peor cuando un programador después de aprender todo esto…
    La coma ejemplo debe ser #define BAR(X) (f(X), g(X)) de lo contrario, el operador de precedencia puede estropear la semántica.
    a pesar de que ambos usted y yo-de-cuatro-y-uno-mitad-años-hace hacer un buen momento, tenemos que vivir en el mundo real. A menos que nos puede garantizar que todas las if declaraciones, etc, en nuestro código se usan llaves, luego envolver los macros como esta es una forma sencilla de evitar problemas.

    OriginalEl autor jfm3

  2. 139

    Macros son copiar y pegar fragmentos de texto en el pre-procesador de poner en el verdadero código de la macro autor espera que el reemplazo se produce un código válido.

    Hay tres buenos «tips» para tener éxito en la que:

    Ayudar a la macro se comportan como auténticos código

    Normal código es generalmente terminaron con un punto y coma. Si el usuario de la vista de código de la no necesidad de una…

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

    Esto significa que el usuario espera que el compilador producirá un error si el semi-colon está ausente.

    Pero el verdadero buena razón es que, en algún momento, la macro del autor tal vez deba reemplazar el macro con una verdadera función (tal vez entre líneas). Por lo que la macro debe realmente comportarse como uno.

    Así que debemos tener una macro necesidad de semi-colon.

    Producir un código válido

    Como se muestra en la jfm3 la respuesta, a veces la macro contiene más de una instrucción. Y si la macro se utiliza dentro de una instrucción if, esto será problemático:

    if(bIsOk)
       MY_MACRO(42) ;

    Este macro podría ser ampliado como:

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

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

    Esto significa que debemos tener para agregar un ámbito para 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 así 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. Así que, de nuevo, la solución es el uso de un ámbito:

    #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.

    La combinación de semi-colon + alcance efectos?

    Hay una C/C++ lenguaje que produce este efecto: El do/while loop:

    do
    {
        //code
    }
    while(false) ;

    La do/while puede crear un ámbito, así encapsulando el código de la macro, y necesita un punto y coma al final, ampliando de esta manera en el código de la necesidad de una.

    El bono?

    El compilador de C++ optimizará lejos de la do/while, como el hecho de su post-condición es falsa, es conocido en tiempo de 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 expanda 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 lejos como

    void doSomething(bool bIsOk)
    {
       int i = 25 ;
    
       if(bIsOk)
       {
          f(43) ; g(43) ;
       }
    
       //Etc.
    }
    Tenga en cuenta que cambiar de macros de función en línea altera alguna norma de macros predefinidas, por ejemplo, el siguiente código muestra un cambio en FUNCIÓN y LÍNEA: #include <stdio.h> #define Fmacro() printf(«%s %d\n», FUNCIÓN, LÍNEA) inline void Finline() { printf(«%s %d\n», FUNCIÓN, LÍNEA); } int main() { Fmacro(); Finline(); return 0; } (en negrita los términos debe ser cerrado por doble subraya mal formateador!)
    Hay un número menor, pero no completamente intrascendente problemas con esta respuesta. Por ejemplo: void doSomething() { int i = 25 ; { int i = x + 1 ; f(i) ; } ; // was MY_MACRO(32) ; } no es la correcta expansión; la x en la expansión debe ser de 32. Un problema más complejo es lo que es la expansión de MY_MACRO(i+7). Y la otra es la expansión de MY_MACRO(0x07 << 6). Hay mucho que es bueno, pero hay algunos undotted i, y no cruzados t.
    Me caso, usted todavía está aquí y no ha di cuenta de esto por ahora: puede escapar de asteriscos y guiones bajos en los comentarios con barras diagonales inversas, así que si usted escribe \_\_LINE\_\_ que hace que lo __LÍNEA__. En mi humilde opinión, es mejor sólo para el uso de formato de código para el código; por ejemplo, __LINE__ (que no requiere ningún tratamiento especial). P. S. no sé si esto era cierto en 2012; han hecho bastantes mejoras para el motor desde entonces.

    OriginalEl autor paercebal

  3. 50

    @jfm3 – tienes una buena respuesta a la pregunta. También puede ser que desee agregar que la macro lenguaje también evita que el posiblemente más peligroso (debido a que no hay error) comportamiento no intencionado con un simple ‘si’ declaraciones:

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

    se expande a:

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

    que es sintácticamente correcta, así que no hay ningún error de compilación, pero tiene la que probablemente sea la consecuencia involuntaria de que g() siempre será llamado.

    «probablemente no deseados»? Me han dicho que «ciertamente no deseados», o bien el programador debe ser llevado a cabo y tiro (como opuesto rotundamente castigó con un látigo).
    O podrían ser dado un aumento, si que están trabajando para una de tres letras de la agencia y subrepticiamente insertar ese código en un ampliamente utilizado un programa de código abierto… 🙂
    Y este comentario me recuerda a la de goto fail línea en la reciente SSL cert de verificación de error se encuentra en los sistemas operativos de Apple

    OriginalEl autor

  4. 23

    Las respuestas anteriores explicar el significado de estas construcciones, pero hay una diferencia significativa entre los dos que no fue mencionado. De hecho, hay una razón para preferir la do ... while a la if ... else construir.

    El problema de la if ... else construir es que no fuerza que ponga el punto y coma. Como en este código:

    FOO(1)
    printf("abc");

    Aunque hayamos dejado fuera el punto y coma (por error), el código se expanda a

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

    y silenciosamente compilar (aunque algunos compiladores pueden emitir una advertencia de código inalcanzable). Pero el printf instrucción no se ejecuta nunca.

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

    Todavía no es tan bueno, porque a partir de la mirada en el macro de la invocación no sabe si se expande a una declaración o a una expresión. Si alguien asume la tarde, ella puede escribir FOO(1),x++; que le volvió a dar un falso positivo. Sólo uso do ... while y eso es todo.
    Documentar la macro para evitar el malentendido debería ser suficiente. Estoy de acuerdo en que do ... while (0) es preferible, pero tiene un inconveniente: Un break o continue va a controlar el do ... while (0) bucle, no es el bucle que contiene la macro invocación. Así que el if truco aún tiene valor.
    No veo donde se puede poner una break o un continue que sería visto como dentro de tus macros do {...} while(0) pseudo-loop. Incluso en el parámetro de macro sería un error de sintaxis.
    Otra razón para utilizar do { ... } while(0) en lugar de if whatever construir, es la idiomática de la naturaleza de la misma. El do {...} while(0) construcción está muy extendida, conocida y utilizada por muchos programadores. Su razón de ser y la documentación es fácilmente reconocidos. No así para el if construir. Se necesita, por tanto, menos esfuerzo para asimilar al hacer la revisión del código.
    He visto a gente escribir macros que tomar bloques de código como argumentos (que no necesariamente se recomienda). Por ejemplo: #define CHECK(call, onerr) if (0 != (call)) { onerr } else (void)0. Podría ser usado como CHECK(system("foo"), break;);, donde el break; es la intención de referirse a la circular que encierra la CHECK() invocación.

    OriginalEl autor ybungalobill

  5. 15

    Mientras que se espera que los compiladores de optimizar la distancia de la do { ... } while(false); bucles, no hay otra solución que no requiere que construir. La solución es utilizar el operador coma:

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

    o incluso más exótico:

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

    Mientras que esto va a funcionar bien con las instrucciones por separado, no va a funcionar con los casos donde las variables se construye y se utiliza como parte de la #define :

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

    Con esto uno se vería obligado a utilizar el do/while construir.

    g (f(X),(X))) es un muy lindo truco!
    gracias, desde el operador coma no garantiza el orden de ejecución, este anidamiento es una manera de hacerlo cumplir.
    Falso; el operador coma es una secuencia punto y lo que no garantía de la ejecución de la orden. Sospecho que se confunde con el de la coma en función de las listas de argumentos.
    La segunda exóticas sugerencia hizo que mi día.
    Sólo quería añadir que los compiladores están obligados a preservar el programa de la conducta observable para la optimización de la do/while no es mucho de un gran problema (suponiendo que las optimizaciones del compilador son correctos).

    OriginalEl autor Marius

  6. 10

    Jens Gustedt del P99 preprocesador biblioteca (sí, el hecho de que tal cosa existe voló mi mente también!) mejora en la if(1) { ... } else construir en un pequeño, pero significativo, de manera definiendo la 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 de esto es que, a diferencia de la do { ... } while(0) construir, break y continue todavía trabajo en el interior del bloque, pero la ((void)0) crea un error de sintaxis si el punto y coma se omite después de la llamada de macro, que de lo contrario, vaya al siguiente bloque. (No hay en realidad un «colgando en el otro» problema aquí, ya que el else se une a la más cercana if, que es el que en la macro.)

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

    A pesar de ser muy inteligente, esto lo hace a uno ser bombardeados con las advertencias del compilador en el potencial colgando más.
    Se suele utilizar macros para crear un medio ambiente controlado, es decir, nunca se uso un break (o continue) dentro de una macro para controlar un bucle que empezaron y terminaron en el exterior, que acaba de mal estilo y oculta los posibles puntos de salida.
    También hay un preprocesador de la biblioteca de refuerzo. Lo que es alucinante?

    OriginalEl autor Isaac Schwabacher

  7. 6

    Por algunas razones que no puedo comentar sobre la primera respuesta…

    Algunos de ustedes mostraron macros con variables locales, pero nadie menciona que no sólo puede utilizar cualquier nombre de una macro! Se va a morder el usuario algún día! Por qué? Debido a que los argumentos de entrada son sustituidos en su plantilla de macro. Y en su macro ejemplos que hemos usado la que probablemente sea la más comúnmente utilizada variabled nombre me.

    Por ejemplo, la siguiente macro

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

    se utiliza en la función siguiente

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

    la macro no se utilice la intención de la variable i, que se declara en el principio de some_func, pero la variable local, que se declara en el bucle do … while de la macro.

    Por lo tanto, nunca se uso común de los nombres de las variables en una macro!

    El patrón habitual es añadir pone de relieve en los nombres de variable en macros – por ejemplo int __i;.
    En realidad es incorrecta e ilegal C; subraya están reservados para su uso por la aplicación. La razón por la que he visto esta «costumbre» es a partir de la lectura de encabezados del sistema (la»aplicación»), que debe limitarse a este espacio de nombres reservados. Para las aplicaciones de las bibliotecas, usted debe elegir su propio oscuro, raro-a-chocan nombres sin subrayado, por ejemplo, mylib_internal___i o similar.
    Tienes razón – de hecho, he leído esto en una «aplicación», el kernel de Linux, pero es una excepción, de todos modos, ya que no utiliza la biblioteca estándar (técnicamente, un «independiente» implementación en C en lugar de un «alojado»).
    esto no es del todo correcto: líder subraya seguido de un capital o de segunda subrayado están reservados para la aplicación en todos los contextos. Líder subraya seguido por algo no están reservados en el ámbito local.
    Sí, pero la distinción es lo suficientemente sutil que me parece la mejor manera de decirle a la gente simplemente no usar estos nombres. La gente que entiende la sutileza, presumiblemente, ya saben que me voy a quitar importancia a los detalles. 🙂

    OriginalEl autor Mike Meyer

  8. 3

    No creo que se mencionó para considerar este

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

    se traduzca en

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

    observe cómo i++ se evalúa dos veces por la macro. Esto puede llevar a algunos interesantes errores.

    Esto no tiene nada que ver con el do … while(0) construir.
    Verdadero. Pero relevante para el tema de macros vs funciones y cómo escribir una macro que se comporta como una función…
    Al igual que la de arriba, esto no es una respuesta, sino un comentario. En el tema: es por eso que el uso de cosas sólo una vez: do { int macroname_i = (i); f(macroname_i); g(macroname_i); } while (/* CONSTCOND */ 0)

    OriginalEl autor John Nilsson

  9. 2

    Explicación

    do {} while (0) y if (1) {} else para asegurarse de que la macro se expande a sólo 1 instrucción. De otro modo:

    if (something)
      FOO(X); 

    podría ampliar a:

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

    Y g(X) iba a ser ejecutado fuera de la if de control de la instrucción. Esto se evita cuando se utiliza do {} while (0) y if (1) {} else.


    Mejor alternativa

    Con un GNU declaración de expresión (no forma parte del estándar de C), usted tiene una mejor manera de do {} while (0) y if (1) {} else para solucionar esto, simplemente utilizando ({}):

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

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

    return FOO("X");
    el uso de bloques de sujeción {} en la macro sería suficiente para envolver el código de la macro para que el servicio sea ejecutado por el mismo si la condición de la ruta. el hacer-mientras que a su alrededor se utiliza para hacer valer un punto y coma en lugares que el macro se utiliza. así, la macro se exige que se comporte de la función por igual. esto incluye el requisito de que el punto y coma final cuando se utiliza.

    OriginalEl autor Cœur

  10. 1

    He encontrado este truco muy útil en situaciones donde usted tiene que secuencialmente el proceso de un determinado valor. En cada nivel de procesamiento, en caso de algún error o no válido condición ocurre, usted puede evitar su posterior procesamiento y salir temprano. por ejemplo,

    #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);
    He visto que se usa de esta manera también.
    Prefiero tener if (process_first() && process_second() && process_third()) { // do whatever, all success } y tienen la libertad de utilizar los argumentos de la función? Esto también le da la flexibilidad para otros valores de retorno en caso de éxito (como >= 0) sin la necesidad de crear una nueva macro.
    Esto no es también una respuesta real a la pregunta…
    esto no va a funcionar bien si usted está teniendo if-else secuencias y poner una macro en el si-bloque…

    OriginalEl autor pankaj

  11. 0

    La razón do {} while (0) se utiliza más de if (1) {} es que no hay manera de que alguien pueda modificar el código antes de llamar a la macro do {} while (0) a ser algún otro tipo de bloque. Por ejemplo, si usted llama a una macro rodeado por if (1) {} como:

    else
      MACRO(x);

    Que es en realidad un else if. Sutil diferencia

    De diez años de edad de respuestas en esta página ya digo que esto.

    OriginalEl autor ericcurtin

Dejar respuesta

Please enter your comment!
Please enter your name here