Why no warning with "#if X" when X undefined?
I occasionally write code something like this:
// file1.cpp
#define DO_THIS 1
#if DO_THIS
// stuff
#endif
During the code development I may switch the definition of DO_THIS
between 0 and 1.
Recently I had to rearrange my source code and copy some code from one file to another. But I found that I had made a mistake and the two parts had become separated like so:
// file1.cpp
#define DO_THIS 1
and
// file2.cpp
#if DO_THIS
// stuff
#endif
Obviously开发者_运维知识库 I fixed the error, but then thought to myself, why didn't the compiler warn me? I have the warning level set to 4. Why isn't #if X
suspicious when X is not defined?
One more question: is there any systematic way I could find out if I've made the same mistake elsewhere? The project is huge.
EDIT: I can understand having no warning with #ifdef that makes perfect sense. But surely #if is different.
gcc can generate a warning for this, but its probably not required by the standard:
-Wundef
Warn if an undefined identifier is evaluated in an `#if' directive.
Again, as it often happens, the answer to the "why" question is just: it was done that way because some time ago it was decided to do it this way. When you use an undefined macro in an #if
it is substituted with 0. You want to know whether it is actually defined - use defined()
directive.
There some interesting benefits to that "default to 0" approach though. Especially when you are using macros that might be defined by the platform, not your own macros.
For example, some platforms offer macros __BYTE_ORDER
, __LITTLE_ENDIAN
and __BIG_ENDIAN
to determine their endianness. You could write preprocessor directive like
#if __BYTE_ORDER == __LITTLE_ENDIAN
/* whatever */
#else
/* whatever */
#endif
But if you try to compile this code on a platform that does not define these non-standard macros at all (i.e. knows nothing about them), the above code will be translated by preprocessor into
#if 0 == 0
...
and the little-endian version of the code will be compiled "by default". If you wrote the original #if
as
#if __BYTE_ORDER == __BIG_ENDIAN
...
then the big-endian version of the code would be compiled "by default".
I can't say that #if
was defined as it was specifically for tricks like the above, but it comes useful at times.
When you can't use a compiler that has a warning message (like -Wundef in gcc), I've found one somewhat useful way to generate compiler errors.
You could of course always write:
#ifndef DO_THIS
error
#endif
#if DO_THIS
But that is really annoying
A slightly less annoying method is:
#if (1/defined(DO_THIS) && DO_THIS)
This will generate a divide by zero error if DO_THIS is undefined. This method is not ideal because the identifier is spelled out twice and a misspelling in the second would put us back where we started. It looks weird too. It seems like there should be a cleaner way to accomplish this, like:
#define PREDEFINED(x) ((1/defined(x)) * x)
#if PREDEFINED(DO_THIS)
but that doesn't actually work.
If you're desperate to prevent this kind of error, try the following which uses preprocessor token-pasting magic and expression evaluation to enforce that a macro is defined (and 0 in this example):
#define DEFINED_VALUE(x,y) (defined (y##x) ? x : 1/x)
#if DEFINED_VALUE(FEATURE1,) == 0
There is recursive issue. In case you have
#define MODEL MODEL_A
#if (MODEL == MODEL_B)
// Surprise, this is compiled!
#endif
where definition of MODEL_A and MODEL_B are missing, then it will compile.
#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
If DO_THIS is yours definition then simple and working solution seems to be usage of Function-like Macro:
#define DO_THIS() 1
#if DO_THIS()
//stuff
#endif
I tested this under Visual Studio 2008, 2015 and GCC v7.1.1. If DO_THIS() is undefined VS gererates:
warning C4067: unexpected tokens following preprocessor directive - expected a newline
and GCC generates
error: missing binary operator before token "("
The compiler didn't generate a warning because this is a preprocessor directive. It's evaluated and resolved before the compiler sees it.
If I'm thinking about this correctly.
Preprocessor directives are handled before any source code is compiled. During that phase(s) of translation in which this occurs all preprocessor directives, macros, etc are handled and then the actual source code is compiled.
Since #if is used to determine if X has been defined and carry out some action if it has or has not been defined. The #if in the code snippet would compile without any errors because there aren't any errors as far as the compiler is concerned. You could always create a header file with specific #defines that your application would need and then include that header.
精彩评论