#ifdef vs #if - ¿cuál es mejor / más seguro como método para habilitar / deshabilitar la compilación de secciones particulares de código?

112

Esto puede ser una cuestión de estilo, pero hay una pequeña división en nuestro equipo de desarrollo y me preguntaba si alguien más tenía alguna idea al respecto ...

Básicamente, tenemos algunas declaraciones de impresión de depuración que desactivamos durante el desarrollo normal. Personalmente prefiero hacer lo siguiente:

//---- SomeSourceFile.cpp ----

#define DEBUG_ENABLED (0)

...

SomeFunction()
{
    int someVariable = 5;

#if(DEBUG_ENABLED)
    printf("Debugging: someVariable == %d", someVariable);
#endif
}

Sin embargo, algunos miembros del equipo prefieren lo siguiente:

// #define DEBUG_ENABLED

...

SomeFunction()
{
    int someVariable = 5;

#ifdef DEBUG_ENABLED
    printf("Debugging: someVariable == %d", someVariable);
#endif
}

... ¿cuál de esos métodos te suena mejor y por qué? Mi sensación es que la primera es más segura porque siempre hay algo definido y no hay peligro de que pueda destruir otras definiciones en otros lugares.

Jon Cage
fuente
Nota: con #if, también puede usarlo #elifde manera coherente, a diferencia de con #ifdef. Por lo tanto, en lugar de simplemente usar #define BLAH, usar #define BLAH 1con #if BLAH, etc ...
Andrew

Respuestas:

82

Mi reacción inicial fue #ifdef, por supuesto , pero creo que en #ifrealidad tiene algunas ventajas significativas para esto: he aquí por qué:

Primero, puede usarlo DEBUG_ENABLEDen preprocesadores y pruebas compiladas. Ejemplo: a menudo, quiero tiempos de espera más largos cuando la depuración está habilitada, por lo que al usar #if, puedo escribir esto

  DoSomethingSlowWithTimeout(DEBUG_ENABLED? 5000 : 1000);

... en vez de ...

#ifdef DEBUG_MODE
  DoSomethingSlowWithTimeout(5000);
#else
  DoSomethingSlowWithTimeout(1000);
#endif

En segundo lugar, está en una mejor posición si desea migrar de una #defineconstante global a una. #defineLa mayoría de los programadores de C ++ suelen desaprobar los s.

Y, en tercer lugar, dice que hay una división en su equipo. Supongo que esto significa que diferentes miembros ya han adoptado diferentes enfoques y es necesario estandarizar. La regla que #ifes la opción preferida significa que el código que se usa #ifdefse compilará y se ejecutará incluso cuando DEBUG_ENABLEDsea ​​falso. Y es mucho más fácil rastrear y eliminar la salida de depuración que se produce cuando no debería ser que viceversa.

Ah, y un pequeño punto de legibilidad. Debería poder usar verdadero / falso en lugar de 0/1 en su #define, y debido a que el valor es un solo token léxico, es la única vez que no necesita paréntesis alrededor.

#define DEBUG_ENABLED true

en vez de

#define DEBUG_ENABLED (1)
Roddy
fuente
Es posible que la constante no se use para habilitar / deshabilitar la depuración, por lo que activar un #ifdef con un #define en 0 podría no ser tan benigno. En cuanto a verdadero / falso, se agregaron en C99 y no existen en C89 / C90.
Michael Carman
Micheal: El / ella estaba abogando contra el uso de #ifdef?!
Jon Cage
7
Sí, un problema con #ifdefes que funciona con cosas que no están definidas; si no están definidos intencionalmente o debido a un error tipográfico o lo que sea.
bames53
6
Tu adición a la respuesta es incorrecta. #if DEBUG_ENBALEDno es un error detectado por el preprocesador. Si DEBUG_ENBALEDno está definido, se expande al token 0en las #ifdirectivas.
R .. GitHub DEJA DE AYUDAR A ICE
6
@R .. En muchos compiladores puede habilitar una advertencia para "#if DEBUG_ENABLED" cuando DEBUG_ENABLED no está definido. En GCC use "-Wundef". En Microsoft Visual Studio, utilice "/ w14668" para activar C4668 como advertencia de nivel 1.
Será el
56

Ambos son horribles. En su lugar, haz esto:

#ifdef DEBUG
#define D(x) do { x } while(0)
#else
#define D(x) do { } while(0)
#endif

Luego, cuando necesite código de depuración, introdúzcalo D();. Y su programa no está contaminado con horribles laberintos de #ifdef.

R .. GitHub DEJA DE AYUDAR A ICE
fuente
6
@MatthieuM. De hecho, creo que la versión original estaba bien. El punto y coma se interpretaría como una declaración vacía. Sin embargo, olvidar el punto y coma podría hacerlo peligroso.
Casey Kuball
31

#ifdef solo verifica si un token está definido, dado

#define FOO 0

luego

#ifdef FOO // is true
#if FOO // is false, because it evaluates to "#if 0"
Terence Simpson
fuente
20

Hemos tenido el mismo problema en varios archivos y siempre existe el problema de que las personas se olvidan de incluir un archivo de "indicador de características" (con una base de código de> 41.000 archivos es fácil de hacer).

Si tuvieras feature.h:

#ifndef FEATURE_H
#define FEATURE_H

// turn on cool new feature
#define COOL_FEATURE 1

#endif // FEATURE_H

Pero luego olvidó incluir el archivo de encabezado en file.cpp:

#if COOL_FEATURE
    // definitely awesome stuff here...
#endif

Entonces tiene un problema, el compilador interpreta que COOL_FEATURE no está definido como "falso" en este caso y no incluye el código. Sí, gcc admite un indicador que causa un error para macros indefinidas ... pero la mayoría del código de terceros define o no define características, por lo que esto no sería tan portátil.

Hemos adoptado una forma portátil de corregir este caso, así como probar el estado de una característica: macros de función.

si cambió el feature.h anterior a:

#ifndef FEATURE_H
#define FEATURE_H

// turn on cool new feature
#define COOL_FEATURE() 1

#endif // FEATURE_H

Pero luego nuevamente olvidó incluir el archivo de encabezado en file.cpp:

#if COOL_FEATURE()
    // definitely awseome stuff here...
#endif

El preprocesador se habría equivocado debido al uso de una macro de función no definida.

Brent Priddy
fuente
17

A los efectos de realizar la compilación condicional, #if y #ifdef son casi lo mismo, pero no del todo. Si su compilación condicional depende de dos símbolos, #ifdef no funcionará tan bien. Por ejemplo, suponga que tiene dos símbolos de compilación condicional, PRO_VERSION y TRIAL_VERSION, es posible que tenga algo como esto:

#if defined(PRO_VERSION) && !defined(TRIAL_VERSION)
...
#else
...
#endif

Usar #ifdef lo anterior se vuelve mucho más complicado, especialmente hacer que la parte #else funcione.

Trabajo en código que usa compilación condicional ampliamente y tenemos una mezcla de #if y #ifdef. Tendemos a usar # ifdef / # ifndef para el caso simple y #if siempre que se evalúen dos o más símbolos.

Mike Thompson
fuente
1
en #if definedqué es defineduna palabra clave o?
nmxprime
15

Creo que es completamente una cuestión de estilo. Ninguno tiene realmente una ventaja obvia sobre el otro.

La consistencia es más importante que cualquier elección en particular, por lo que le recomiendo que se reúnan con su equipo y elijan un estilo y lo mantengan.

Derek Park
fuente
8

Yo mismo prefiero:

#if defined(DEBUG_ENABLED)

Dado que facilita la creación de código que busca la condición opuesta, es mucho más fácil de detectar:

#if !defined(DEBUG_ENABLED)

vs.

#ifndef(DEBUG_ENABLED)
Jim Buck
fuente
8
Personalmente, creo que es más fácil pasar por alto ese pequeño signo de exclamación.
Jon Cage
6
¿Con resaltado de sintaxis? :) En el resaltado de sintaxis, la "n" en "ifndef" es mucho más difícil de detectar ya que es del mismo color.
Jim Buck
De acuerdo, quise decir que #ifndef es más fácil de detectar que #if! Definido cuando se compara con #if definido ... pero dado todo #if definido / # si! Definido vs # ifdef / # ifndef, ¡cualquiera de los dos es igualmente legible!
Jon Cage
@JonCage Sé que han pasado algunos años desde este comentario, pero me gustaría señalar que puedes escribirlo #if ! definedpara que sea !más prominente y difícil de pasar por alto.
Pharap
@Pharap - Eso ciertamente parece una mejora :)
Jon Cage
7

Es cuestión de estilo. Pero recomiendo una forma más concisa de hacer esto:

#ifdef USE_DEBUG
#define debug_print printf
#else
#define debug_print
#endif

debug_print("i=%d\n", i);

Haz esto una vez, luego usa siempre debug_print () para imprimir o no hacer nada. (Sí, esto se compilará en ambos casos). De esta manera, su código no se alterará con las directivas del preprocesador.

Si recibe la advertencia "la expresión no tiene efecto" y desea deshacerse de ella, aquí tiene una alternativa:

void dummy(const char*, ...)
{}

#ifdef USE_DEBUG
#define debug_print printf
#else
#define debug_print dummy
#endif

debug_print("i=%d\n", i);
Lev
fuente
3
Quizás la macro de impresión no fue el mejor ejemplo después de todo; de hecho, ya lo hacemos en nuestra base de código para nuestro código de depuración más estándar. Usamos los bits #if / #ifdefined para las áreas en las que es posible que desee activar la depuración adicional ...
Jon Cage
5

#ifle da la opción de configurarlo en 0 para desactivar la funcionalidad, mientras sigue detectando que el interruptor está allí.
Personalmente, siempre #define DEBUG 1puedo detectarlo con un #if o #ifdef

Martin Beckett
fuente
1
Esto falla, ya que #define DEBUG = 0 ahora no se ejecutará #if sino que se ejecutará #ifdef
tloach
1
Ese es el punto, puedo eliminar DEBUG por completo o simplemente configurarlo en 0 para deshabilitarlo.
Martin Beckett
debería ser #define DEBUG 1. Not#define DEBUG=1
Keshava GN
4

#if y #define MY_MACRO (0)

El uso de #if significa que creó una macro "definir", es decir, algo que se buscará en el código para ser reemplazado por "(0)". Este es el "infierno macro" que odio ver en C ++, porque contamina el código con posibles modificaciones de código.

Por ejemplo:

#define MY_MACRO (0)

int doSomething(int p_iValue)
{
   return p_iValue + 1 ;
}

int main(int argc, char **argv)
{
   int MY_MACRO = 25 ;
   doSomething(MY_MACRO) ;

   return 0;
}

da el siguiente error en g ++:

main.cpp|408|error: lvalue required as left operand of assignment|
||=== Build finished: 1 errors, 0 warnings ===|

Solo un error.

Lo que significa que su macro interactuó exitosamente con su código C ++: la llamada a la función fue exitosa. En este simple caso, es divertido. Pero mi propia experiencia con macros jugando silenciosamente con mi código no está llena de alegría y plenitud, así que ...

#ifdef y #define MY_MACRO

Usar #ifdef significa que "define" algo. No es que le des un valor. Todavía es contaminante, pero al menos, será "reemplazado por nada", y el código C ++ no lo verá como una declaración de código lagítima. El mismo código anterior, con una simple definición, es:

#define MY_MACRO

int doSomething(int p_iValue)
{
   return p_iValue + 1 ;
}

int main(int argc, char **argv)
{
   int MY_MACRO = 25 ;
   doSomething(MY_MACRO) ;

   return 0;
}

Da las siguientes advertencias:

main.cpp||In function int main(int, char**)’:|
main.cpp|406|error: expected unqualified-id before ‘=’ token|
main.cpp|399|error: too few arguments to function int doSomething(int)’|
main.cpp|407|error: at this point in file|
||=== Build finished: 3 errors, 0 warnings ===|

Entonces...

Conclusión

Prefiero vivir sin macros en mi código, pero por múltiples razones (definir protecciones de encabezado o depurar macros), no puedo.

Pero al menos, me gusta hacerlos lo menos interactivos posible con mi código C ++ legítimo. Lo que significa usar #define sin valor, usar #ifdef y #ifndef (o incluso #if definido como lo sugiere Jim Buck), y sobre todo, darles nombres tan largos y tan extraños que nadie en su sano juicio usará "por casualidad", y que de ninguna manera afectará al código C ++ legítimo.

Post Scriptum

Ahora, mientras vuelvo a leer mi publicación, me pregunto si no debería intentar encontrar algún valor que nunca sea C ++ correcto para agregar a mi definición. Algo como

#define MY_MACRO @@@@@@@@@@@@@@@@@@

que podría usarse con #ifdef y #ifndef, pero no permitir que el código se compile si se usa dentro de una función ... Intenté esto con éxito en g ++, y dio el error:

main.cpp|410|error: stray ‘@’ in program|

Interesante. :-)

paercebal
fuente
Estoy de acuerdo en que las macros pueden ser peligrosas, pero ese primer ejemplo sería bastante obvio de depurar y, por supuesto, solo da un error. ¿Por qué esperarías más? He visto errores mucho más desagradables como resultado de macros ...
Jon Cage
Es cierto que la diferencia entre una solución y otra es casi trivial. Pero en este caso, como estamos hablando de dos estilos de codificación en competencia, ni siquiera lo trivial puede ser ignorado, porque después de eso, todo lo que queda es el gusto personal (y en ese punto, creo que no debería normalizarse )
paercebal
4

Eso no es una cuestión de estilo en absoluto. Además, la pregunta es, lamentablemente, incorrecta. No puede comparar estas directivas de preprocesador en el sentido de mejor o más seguro.

#ifdef macro

significa "si la macro está definida" o "si la macro existe". El valor de la macro no importa aquí. Puede ser lo que sea.

#if macro

si siempre se compara con un valor. En el ejemplo anterior, es la comparación implícita estándar:

#if macro !=0

ejemplo para el uso de #if

#if CFLAG_EDITION == 0
    return EDITION_FREE;
#elif CFLAG_EDITION == 1
    return EDITION_BASIC;
#else
    return EDITION_PRO;
#endif

ahora puede poner la definición de CFLAG_EDITION en su código

#define CFLAG_EDITION 1 

o puede establecer la macro como bandera del compilador. Vea también aquí .

tmanthey
fuente
2

El primero me parece más claro. Parece más natural convertirlo en una bandera en comparación con definido / no definido.

axblount
fuente
2

Ambos son exactamente equivalentes. En el uso idiomático, #ifdef se usa solo para verificar la definición (y lo que usaría en su ejemplo), mientras que #if se usa en expresiones más complejas, como #if definido (A) &&! Definido (B).

zvrba
fuente
1
El OP no preguntaba cuál es mejor entre "#ifdef" y "#if definido", sino entre "# ifdef / # si está definido" y "#if".
vástago
1

Un poco de OT, pero activar / desactivar el registro con el preprocesador es definitivamente subóptimo en C ++. Hay buenas herramientas de registro como log4cxx de Apache que son de código abierto y no restringen la forma en que distribuye su aplicación. También le permiten cambiar los niveles de registro sin recompilación, tienen una sobrecarga muy baja si apaga el registro y le dan la oportunidad de desactivarlo por completo en producción.

David Nehme
fuente
1
Estoy de acuerdo, y de hecho lo hacemos en nuestro código, solo quería un ejemplo de algo para lo que podrías usar #if, etc.
Jon Cage
1

Solía ​​usar #ifdef , pero cuando cambié a Doxygen para la documentación, descubrí que las macros comentadas no se pueden documentar (o, al menos, Doxygen produce una advertencia). Esto significa que no puedo documentar las macros de cambio de función que no están habilitadas actualmente.

Aunque es posible definir las macros solo para Doxygen, esto significa que las macros en las partes no activas del código también se documentarán. Personalmente, quiero mostrar los cambios de función y, de lo contrario, solo documentar lo que está seleccionado actualmente. Además, hace que el código sea bastante complicado si hay muchas macros que deben definirse solo cuando Doxygen procesa el archivo.

Por tanto, en este caso, es mejor definir siempre las macros y utilizar #if.

StefanB
fuente
0

Siempre he usado #ifdef y banderas del compilador para definirlo ...

tloach
fuente
¿Alguna razón en particular (por curiosidad)?
Jon Cage
2
Para ser honesto, nunca pensé en eso, solo en cómo se ha hecho en los lugares en los que he trabajado. Ofrece la ventaja de que, en lugar de realizar un cambio de código para una compilación de producción, todo lo que tiene que hacer es 'hacer DEBUG' para depurar, o 'hacer PRODUCCIÓN' para regular
tloach
0

Alternativamente, puede declarar una constante global y usar C ++ if, en lugar del preprocesador #if. El compilador debería optimizar las ramas no utilizadas para usted, y su código será más limpio.

Esto es lo que dice C ++ Gotchas de Stephen C. Dewhurst sobre el uso de # if.

Dima
fuente
1
Esa es una pésima solución, tiene los siguientes problemas: 1. Solo funciona en funciones, no puede eliminar variables de clase innecesarias, etc. 2. Los compiladores pueden arrojar advertencias sobre el código inalcanzable 3. El código en el if aún necesita compilarse, lo que significa tienes que mantener todas tus funciones de depuración definidas, etc.
Don Neufeld
Primero, la pregunta era específicamente sobre debug printfs, por lo que las variables de clase innecesarias no son un problema aquí. En segundo lugar, dadas las capacidades de los compiladores modernos, debe usar #ifdefs lo menos posible. En la mayoría de los casos, puede utilizar configuraciones de compilación o especializaciones de plantillas.
Dima
0

Hay una diferencia en el caso de una forma diferente de especificar una definición condicional para el controlador:

diff <( echo | g++ -DA= -dM -E - ) <( echo | g++ -DA -dM -E - )

salida:

344c344
< #define A 
---
> #define A 1

Esto significa que -DAes sinónimo de -DA=1y si se omite el valor, puede generar problemas en caso de #if Auso.

Tomilov Anatoliy
fuente
0

Me gusta #define DEBUG_ENABLED (0)cuando es posible que desee varios niveles de depuración. Por ejemplo:

#define DEBUG_RELEASE (0)
#define DEBUG_ERROR (1)
#define DEBUG_WARN (2)
#define DEBUG_MEM (3)
#ifndef DEBUG_LEVEL
#define DEBUG_LEVEL (DEBUG_RELEASE)
#endif
//...

//now not only
#if (DEBUG_LEVEL)
//...
#endif

//but also
#if (DEBUG_LEVEL >= DEBUG_MEM)
LOG("malloc'd %d bytes at %s:%d\n", size, __FILE__, __LINE__);
#endif

Hace que sea más fácil depurar fugas de memoria, sin tener todas esas líneas de registro en su forma de depurar otras cosas.

Además, #ifndefalrededor de la definición hace que sea más fácil elegir un nivel de depuración específico en la línea de comandos:

make -DDEBUG_LEVEL=2
cmake -DDEBUG_LEVEL=2
etc

Si no fuera por esto, le daría ventaja #ifdefporque el indicador del compilador / marca sería anulado por el del archivo. Por lo tanto, no tiene que preocuparse por volver a cambiar el encabezado antes de realizar la confirmación.

memtha
fuente