versiones - visual studio c++
¿Por qué no hay una advertencia con "#if X" cuando X no está definida? (8)
De vez en cuando escribo un código como este:
// file1.cpp
#define DO_THIS 1
#if DO_THIS
// stuff
#endif
Durante el desarrollo del código puedo cambiar la definición de DO_THIS
entre 0 y 1.
Recientemente tuve que reorganizar mi código fuente y copiar un código de un archivo a otro. Pero descubrí que había cometido un error y que las dos partes se habían separado así:
// file1.cpp
#define DO_THIS 1
y
// file2.cpp
#if DO_THIS
// stuff
#endif
Obviamente solucioné el error, pero luego pensé: ¿por qué el compilador no me advirtió? Tengo el nivel de advertencia establecido en 4. ¿Por qué no es sospechoso #if X
cuando X no está definida?
Una pregunta más: ¿hay alguna manera sistemática de averiguar si he cometido el mismo error en otra parte? El proyecto es enorme.
EDITAR: Puedo entender que no tengo ninguna advertencia con #ifdef que tiene perfecto sentido. Pero seguro que #if es diferente.
Cuando no puedes usar un compilador que tiene un mensaje de advertencia (como -Wundef en gcc), he encontrado una manera algo útil de generar errores de compilación.
Por supuesto, siempre podrías escribir:
#ifndef DO_THIS
error
#endif
#if DO_THIS
Pero eso es realmente molesto.
Un método un poco menos molesto es:
#if (1/defined(DO_THIS) && DO_THIS)
Esto generará un error de división por cero si DO_THIS no está definido. Este método no es ideal porque el identificador se deletrea dos veces y una falta de ortografía en el segundo nos volvería a poner donde empezamos. Se ve raro también. Parece que debería haber una forma más limpia de lograr esto, como:
#define PREDEFINED(x) ((1/defined(x)) * x)
#if PREDEFINED(DO_THIS)
pero eso en realidad no funciona.
El compilador no generó una advertencia porque se trata de una directiva de preprocesador. Se evalúa y resuelve antes de que el compilador lo vea.
Hay problema recursivo. En caso de que tengas
#define MODEL MODEL_A
#if (MODEL == MODEL_B)
// Surprise, this is compiled!
#endif
donde falta la definición de MODEL_A y MODEL_B, se compilará.
#ifdef MODEL
#error Sorry, MODEL Not Defined
// Surprise, this error is never reached (MODEL was defined by undefined symbol!)
#endif
#ifdef MODEL_B
#error Sorry, MODEL_B Not Defined
// This error is reached
#endif
Nuevamente, como suele ocurrir, la respuesta a la pregunta del "por qué" es justa: se hizo de esa manera porque hace un tiempo se decidió hacerlo de esta manera. Cuando utiliza una macro no definida en un #if
si se sustituye por 0. Desea saber si está realmente definido: use la directiva defined()
.
Sin embargo, hay algunos beneficios interesantes para ese enfoque "predeterminado a 0". Especialmente cuando está utilizando macros que podrían estar definidos por la plataforma, no sus propias macros.
Por ejemplo, algunas plataformas ofrecen las macros __BYTE_ORDER
, __LITTLE_ENDIAN
y __BIG_ENDIAN
para determinar su endianness. Podrías escribir una directiva de preprocesador como
#if __BYTE_ORDER == __LITTLE_ENDIAN
/* whatever */
#else
/* whatever */
#endif
Pero si intenta compilar este código en una plataforma que no define estas macros no estándar (es decir, no sabe nada de ellas), el preprocesador traducirá el código anterior al
#if 0 == 0
...
y la versión little-endian del código se compilará "por defecto". Si escribiste el #if
original como
#if __BYTE_ORDER == __BIG_ENDIAN
...
entonces la versión big-endian del código se compilaría "por defecto".
No puedo decir que #if
se definió como lo fue específicamente para trucos como el anterior, pero a veces resulta útil.
Si DO_THIS es su definición, entonces una solución simple y funcional parece ser el uso de una macro similar a una función:
#define DO_THIS() 1
#if DO_THIS()
//stuff
#endif
Probé esto bajo Visual Studio 2008, 2015 y GCC v7.1.1. Si DO_THIS () no está definido VS gererados:
advertencia C4067: tokens inesperados después de la directiva del preprocesador - se esperaba una nueva línea
y GCC genera
error: falta el operador binario antes del token "("
Si está desesperado por evitar este tipo de error, intente lo siguiente, que utiliza la magia de pegado de token del preprocesador y la evaluación de expresiones para imponer que se define una macro (y 0 en este ejemplo):
#define DEFINED_VALUE(x,y) (defined (y##x) ? x : 1/x)
#if DEFINED_VALUE(FEATURE1,) == 0
Si estoy pensando en esto correctamente.
Las directivas de preprocesador se manejan antes de compilar cualquier código fuente. Durante esa (s) fase (s) de traducción en la que esto ocurre, todas las directivas de preprocesador, macros, etc. se manejan y luego se compila el código fuente real.
Dado que #if se utiliza para determinar si X se ha definido y llevar a cabo alguna acción si se ha definido o no. El #if en el fragmento de código se compilaría sin ningún error porque no hay ningún error en lo que respecta al compilador. Siempre puede crear un archivo de encabezado con las # definiciones específicas que su aplicación necesitaría y luego incluir ese encabezado.
gcc puede generar una advertencia para esto, pero probablemente no sea requerido por el estándar:
-Wundef
Avisar si un identificador no definido se evalúa en una directiva `#if ''.