c++ - que - #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?
endif c++ (18)
#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ó con éxito con su código C ++: la llamada a la función fue exitosa. En este caso simple, es divertido. Pero mi propia experiencia con macros que juegan silenciosamente con mi código no está llena de alegría y plenitud, así que ...
#ifdef y #define MY_MACRO
Usar #ifdef significa que "defines" algo. No es que le des un valor. Todavía es contaminante, pero al menos, será "reemplazado por nada", y no será visto por el código C ++ como una declaración de código lagitima. El mismo código anterior, con una definición simple, it:
#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 ===|
Asi que...
Conclusión
Prefiero vivir sin macros en mi código, pero por varias razones (definición de guardias de encabezado o macros de depuración), no puedo.
Pero al menos, me gusta hacerlos lo menos interactivos posibles con mi código legítimo de C ++. Lo que significa usar #define sin valor, usando #ifdef y #ifndef (o incluso # si se define como lo sugiere Jim Buck), y sobre todo, dándoles nombres tan largos y tan extraños que nadie en su sano juicio usará es "por casualidad", y que de ninguna manera afectará el código legítimo de C ++.
Post Scriptum
Ahora, mientras estoy volviendo a leer mi publicación, me pregunto si no debería tratar de encontrar algún valor que nunca sea correcto C ++ para agregar a mi definición. Algo como
#define MY_MACRO @@@@@@@@@@@@@@@@@@
que podría usarse con #ifdef y #ifndef, pero no permitir la compilación de código si se usa dentro de una función ... Intenté esto con éxito en g ++, y me dio el siguiente error:
main.cpp|410|error: stray ‘@’ in program|
Interesante. :-)
Esto puede ser una cuestión de estilo, pero hay un poco de división en nuestro equipo de desarrollo y me pregunté si alguien más tenía alguna idea al respecto ...
Básicamente, tenemos algunas declaraciones de impresión de depuración que apagamos 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
}
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 a otra persona.
A los efectos de realizar una compilación condicional, #if y #ifdef son casi lo mismo, pero no del todo. Si su compilación condicional depende de dos símbolos, entonces #ifdef no funcionará tan bien. Por ejemplo, supongamos que tiene dos símbolos de compilación condicionales, PRO_VERSION y TRIAL_VERSION, podría tener algo como esto:
#if defined(PRO_VERSION) && !defined(TRIAL_VERSION)
...
#else
...
#endif
Al usar #ifdef, lo anterior se vuelve mucho más complicado, especialmente al hacer que la parte #else funcione.
Trabajo en código que usa la compilación condicional de manera extensiva 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.
Alternativamente, puede declarar una constante global y usar C ++ si, en lugar del preprocesador #if. El compilador debería optimizar las ramas no utilizadas para usted, y su código será más limpio.
Aquí está lo que C ++ Gotchas de Stephen C. Dewhurst dice sobre el uso de # if''s.
Ambos son exactamente equivalentes. En el uso idiomático, #ifdef se usa solo para verificar la definición (y lo que utilizaría en su ejemplo), mientras que #if se usa en expresiones más complejas, como #if defined (A) &&! Defined (B).
Ambos son horribles. En cambio, haz esto:
#ifdef DEBUG
#define D(x) do { x } while(0)
#else
#define D(x) do { } while(0)
#endif
Luego, siempre que necesite código de depuración, colóquelo dentro de D();
. Y tu programa no está contaminado con horribles laberintos de #ifdef
.
Creo que es una cuestión de estilo. Ninguno de los dos tiene una ventaja obvia sobre el otro.
La consistencia es más importante que cualquiera de las opciones en particular, por lo que te recomiendo que te unas a tu equipo y escojas un estilo, y te apegues a él.
El primero me parece más claro. Parece más natural convertirlo en una bandera en comparación con definido / no definido.
Es una 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);
Haga esto una vez, luego siempre use debug_print () para imprimir o no hacer nada. (Sí, esto compilará en ambos casos.) De esta manera, su código no se confundirá con las directivas de preprocesador.
Si recibes la advertencia "la expresión no tiene efecto" y quieres deshacerte de ella, aquí tienes 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);
Eso no es una cuestión de estilo en absoluto. Además, lamentablemente, la pregunta es incorrecta. No puede comparar estas directivas de preprocesador en el sentido de mejor o más seguro.
#ifdef macro
significa "si se define macro" o "si macro existe". El valor de 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 indicador del compilador. Ver también aquí .
Existe una diferencia en caso de que haya una manera 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 -DA
es sinónimo de -DA=1
y si se omite el valor, entonces puede generar problemas en caso de uso de #if A
Hemos tenido este mismo problema en varios archivos y siempre existe el problema de que las personas se olviden de incluir un archivo de "marcas de características" (con una base de código de> 41,000 archivos es fácil de hacer).
Si tuviera feature.h:
#ifndef FEATURE_H
#define FEATURE_H
// turn on cool new feature
#define COOL_FEATURE 1
#endif // FEATURE_H
Pero luego olvidaste incluir el archivo de cabecera 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 es compatible con un indicador que causa un error en macros indefinidas ... pero la mayoría de los códigos de terceros definen o no definen características, por lo que no sería portátil.
Hemos adoptado una forma portátil de corregir este caso, así como probar el estado de una función: macros de función.
si cambió la característica anterior.h a:
#ifndef FEATURE_H
#define FEATURE_H
// turn on cool new feature
#define COOL_FEATURE() 1
#endif // FEATURE_H
Pero luego se olvidó de incluir el archivo de encabezado en file.cpp:
#if COOL_FEATURE()
// definitely awseome stuff here...
#endif
El preprocesador habría cometido un error debido al uso de una macro de función no definida.
Mi reacción inicial fue #ifdef
, por supuesto , pero creo que #if
realidad tiene algunas ventajas significativas para esto, aquí está el por qué:
Primero, puede usar DEBUG_ENABLED
en el preprocesador y las pruebas compiladas. Ejemplo: a menudo, quiero tiempos de espera más largos cuando la depuración está habilitada, así que si uso #if
, puedo escribir esto
DoSomethingSlowWithTimeout(DEBUG_ENABLED? 5000 : 1000);
... en lugar de ...
#ifdef DEBUG_MODE
DoSomethingSlowWithTimeout(5000);
#else
DoSomethingSlowWithTimeout(1000);
#endif
En segundo lugar, estás en una mejor posición si deseas migrar de un #define
a una constante global. #define
s generalmente son mal vistos por la mayoría de los programadores de C ++.
Y, Tercero, dices que tienes una división en tu equipo. Supongo que esto significa que diferentes miembros ya han adoptado diferentes enfoques, y es necesario estandarizarlos. #if
que #if
es la opción preferida significa que el código que usa #ifdef
compilará y DEBUG_ENABLED
incluso cuando DEBUG_ENABLED
sea falso. Y es mucho más fácil rastrear y eliminar la salida de depuración que se produce cuando no debería ser, y viceversa.
Ah, y un punto de lectura menor. Debería poder usar verdadero / falso en lugar de 0/1 en su #define
, y dado que el valor es un único token léxico, es la única vez que no necesita paréntesis a su alrededor.
#define DEBUG_ENABLED true
en lugar de
#define DEBUG_ENABLED (1)
Siempre he usado #ifdef y los indicadores del compilador para definirlo ...
Solía usar #ifdef
, pero cuando #ifdef
a Doxygen por 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 funciones que no están habilitadas actualmente.
Aunque es posible definir las macros solo para Doxygen, esto significa que también se documentarán las macros en las partes no activas del código. Personalmente, quiero mostrar los conmutadores de funciones y 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 lo tanto, en este caso, es mejor definir siempre las macros y usar #if
.
Un poco de OT, pero encender / apagar el registro con el preprocesador definitivamente no es óptimo en C ++. Hay buenas herramientas de registro como log4cxx de Apache, que son de código abierto y no restringen la forma de distribuir su aplicación. También le permiten cambiar los niveles de registro sin recompilación, tener un costo muy bajo si apaga el inicio de sesión, y darle la oportunidad de desconectar completamente el proceso de producción.
Yo mismo prefiero:
#if defined(DEBUG_ENABLED)
Ya que facilita la creación de código que busca la condición opuesta mucho más fácil de detectar:
#if !defined(DEBUG_ENABLED)
vs.
#ifndef(DEBUG_ENABLED)
#if
le ofrece la opción de configurarlo en 0 para desactivar la funcionalidad, al mismo tiempo que detecta que el interruptor está allí.
Personalmente siempre defino DEPUD #define DEBUG 1
para que pueda verlo con un #if o #ifdef
#ifdef
solo comprueba si se define un token, dado
#define FOO 0
entonces
#ifdef FOO // is true
#if FOO // is false, because it evaluates to "#if 0"