36

I regularly use object-like preprocessor macros as boolean flags in C code to turn on and off sections of code.

For example

#define DEBUG_PRINT 1

And then use it like

#if(DEBUG_PRINT == 1)
    printf("%s", "Testing");
#endif

However, it comes a problem if the header file that contains the #define is forgotten to be included in the source code. Since the macro is not declared, the preprocessor treats it as if it equals 0, and the #if statement never runs.

When the header file is forgotten to be included, non-expected, unruly behaviour can occur.

Ideally, I would like to be able to both check that a macro is defined, and check that it equals a certain value, in one line. If it is not defined, the preprocessor throws an error (or warning).

I'm looking for something along the lines of:

#if-def-and-true-else-throw-error(DEBUG_PRINT)
    ...
#endif

It's like a combination of #ifdef and #if, and if it doesn't exist, uses #error.

I have explored a few avenues, however, preprocessor directives can't be used inside a #define block, and as far as I can tell, there is no preprocessor option to throw errors/warnings if a macro is not defined when used inside a #if statement.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
gbmhunter
  • 1,747
  • 3
  • 23
  • 24
  • I don't get why you don't just use `#if DEBUG_PRINT == 1`. In the case where `DEBUG_PRINT` is not defined, this becomes `#if 0 == 1`, which is the behavior you want. – R.. GitHub STOP HELPING ICE Jun 18 '13 at 04:55
  • That doesn't produce the error, though. – Carl Norum Jun 18 '13 at 04:55
  • Not if you have made `DEBUG_PRINT == 1` in a header file, but forgotten to `#include` it in the .c file. – gbmhunter Jun 18 '13 at 04:57
  • 2
    The compiler can't save you from everything... – Carl Norum Jun 18 '13 at 04:57
  • @Carl - I arrived at this idea after reading about the philosophy of preventing bugs before they can happen. – gbmhunter Jun 18 '13 at 05:02
  • 1
    Right. Then what you want is to wrap your `print` function in a macro or another function, rather than wrapping it at all of the call locations. Then you can consolidate the error generation code in a single place rather than trying to sprinkle it all over your project. – Carl Norum Jun 18 '13 at 05:04
  • 1
    You could just as easily forget to use `#if-def-and-true-else-throw-error(DEBUG_PRINT)` instead of `#if DEBUG_PRINT == 1`, so even if this feature existed it wouldn't achieve anything useful. – Jim Balter Jun 18 '13 at 05:17
  • In an embedded application where space is restrictive, you want to commend out the debug message if not being used. There's no way of stopping PrintDebug("My space consuming debug message"); from consuming space if the #define controls the internals of this function rather than the call to it. – gbmhunter Jun 18 '13 at 05:17
  • 2
    If you defined PrintDebug in the header file -- which is the logical place for it -- then the code won't compile if the header file isn't included. – Jim Balter Jun 18 '13 at 05:21
  • 1
    `gcc` has an option (`-Wundef`) to generate a warning when an undefined identifier is evaluated in an `#if` directive. – devnull Jun 18 '13 at 05:25
  • @Jim I'm not sure what you mean about defining PrintDebug in the header file? – gbmhunter Jun 18 '13 at 10:42
  • Eh? I already posted an answer and you already commented on it, 5 hours before the above question. – Jim Balter Jun 18 '13 at 17:55
  • @Jim Oh sorry, yes you did. For some reason when I read "If you defined PrintDebug in the header file", I was thinking about C definitions, not preprocessor #defines. – gbmhunter Jun 18 '13 at 20:55
  • Possible duplicate of [C macro: #if check for equality](https://stackoverflow.com/questions/2303963/c-macro-if-check-for-equality) – Eugen Konkov Jan 23 '18 at 13:45

6 Answers6

15

as far as I can tell, there is no preprocessor option to throw errors/warnings if a macro is not defined when used inside a #if statement.

It can't be an error because the C standard specifies that behavior is legal. From section 6.10.1/3 of ISO C99 standard:

After all replacements due to macro expansion and the defined unary operator have been performed, all remaining identifiers are replaced with the pp-number 0....

As Jim Balter notes in the comment below, though, some compilers (such as gcc) can issue warnings about it. However, since the behavior of substituting 0 for unrecognized preprocessor tokens is legal (and in many cases desirable), I'd expect that enabling such warnings in practice would generate a significant amount of noise.

There's no way to do exactly what you want. If you want to generate a compilation failure if the macro is not defined, you'll have to do it explicitly

#if !defined DEBUG_PRINT
#error DEBUG_PRINT is not defined.
#endif

for each source file that cares. Alternatively, you could convert your macro to a function-like macro and avoid using #if. For example, you could define a DEBUG_PRINT macro that expands to a printf call for debug builds but expands to nothing for non-debug builds. Any file that neglects to include the header defining the macro then would fail to compile.


Edit:

Regarding desirability, I have seen numerous times where code uses:

#if ENABLE_SOME_CODE
...
#endif

instead of:

#ifdef ENABLE_SOME_CODE
...
#endif

so that #define ENABLE_SOME_CODE 0 disables the code rather than enables it.

jamesdlin
  • 81,374
  • 13
  • 159
  • 204
  • 2
    "This is true because the C standard specifies that behavior." -- Not really. The standard does not disallow implementations from issuing diagnostics about conforming programs, and gcc's -Wundef will do just that in this case. – Jim Balter Jun 18 '13 at 05:54
  • 1
    devnull mentioned -Wundef in the comments above; I just repeated it here. "and in many cases desirable" -- I think it's rather poor programming practice. "I'd expect that enabling such warnings in practice would generate a significant amount of noise." -- Try it and see ... you're more likely to catch bugs, I think. – Jim Balter Jun 18 '13 at 06:29
15

This may not work for the general case (I don't think there's a general solution to what you're asking for), but for your specific example you might consider changing this sequence of code:

#if(DEBUG_PRINT == 1)
    printf("%s", "Testing");
#endif

to:

if (DEBUG_PRINT == 1) {
    printf("%s", "Testing");
}

It's no more verbose and will fail to compile if DEBUG_PRINT is not defined or if it's defined to be something that cannot be compared with 1.

Michael Burr
  • 333,147
  • 50
  • 533
  • 760
  • 1
    Ahhhh...good thinking. I first thought, nooo, this isn't good, what about the extra execution time introduced (I come from an embedded background)! But then again this is one of the simplest optimisations a compiler could do, isn't it? – gbmhunter Jun 18 '13 at 05:26
  • @gbmhunter, yes in this case `if` statement itself is not included when DEBUG is defined either as `0` or `1`, when not defined undeclared identifier is thrown. Cant affect the performance in that way. Verified with linux/gcc-4.7.2. – VoidPointer Jun 18 '13 at 05:41
  • 4
    @VoidPointer " Cant affect the performance in that way." -- Of course it can. Whether it will, for any given implementation and set of options, is another matter. "Verified with linux/gcc-4.7.2." -- You can't verify general claims with an anecdote. – Jim Balter Jun 18 '13 at 05:56
  • 8
    If your compiler doesn't eliminate the `if` test for a simple comparison of two integer literals at compile time (at least for a non-debug build), then you may want to look to another toolchain. Nearly all compilers will elide the test (and the statement if the test evaluates to false) in the generated runtime code as long as some level of optimization is enabled. – Michael Burr Jun 18 '13 at 14:47
  • Some compliers will throw an exception because if DEBUG_PRINT is 0 it will never execute. – zenchemical May 31 '23 at 17:01
12

Rather than using DEBUG_PRINT directly in your source files, put this in the header file:

#if !defined(DEBUG_PRINT)
    #error DEBUG_PRINT is not defined
#endif

#if DEBUG_PRINT
    #define PrintDebug([args]) [definition]
#else
    #define PrintDebug
#endif

Any source file that uses PrintDebug but doesn't include the header file will fail to compile.

If you need other code than calls to PrintDebug to be compiled based on DEBUG_PRINT, consider using Michael Burr's suggestion of using plain if rather than #if (yes, the optimizer will not generate code within a false constant test).

Edit: And you can generalize PrintDebug above to include or exclude arbitrary code as long as you don't have commas that look like macro arguments:

#if !defined(IF_DEBUG)
    #error IF_DEBUG is not defined
#endif

#if IF_DEBUG
    #define IfDebug(code) code
#else
    #define IfDebug(code)
#endif

Then you can write stuff like

IfDebug(int count1;)  // IfDebug(int count1, count2;) won't work
IfDebug(int count2;)
...
IfDebug(count1++; count2++;)
Jim Balter
  • 16,163
  • 3
  • 43
  • 66
  • Good thinking, for the particular solution of debug printing. However, I use the macro switch concept for more than just debug printing. It wouldn't work for inclusion of variable amounts of different code. – gbmhunter Jun 18 '13 at 05:29
  • @gbmhunter You already have the solution to that, which I added as an edit while you were adding your comment. – Jim Balter Jun 18 '13 at 05:31
10

Yes you can check both:

#if defined DEBUG  &&  DEBUG == 1
#  define D(...) printf(__VA_ARGS__)
#else
#  define D(...)
#endif

In this example even when #define DEBUG 0 but it is not equal to 1 thus nothing will be printed.

You can do even this:

#if defined DEBUG  &&  DEBUG
#  define D(...) printf(__VA_ARGS__)
#else
#  define D(...)
#endif

Here if you #define DEBUG 0 and then D(1,2,3) also nothing will be printed

DOC

Eugen Konkov
  • 22,193
  • 17
  • 108
  • 158
0

Simply create a macro DEBUG_PRINT that does the actual printing:

#define DEBUG_PRINT(n, str)    \
                               \
  if(n == 1)                   \
  {                            \
    printf("%s", str);         \
  }                            \
  else if(n == 2)              \
  {                            \
    do_something_else();       \
  }                            \
                               \
#endif


#include <stdio.h>

int main()
{
  DEBUG_PRINT(1, "testing");
}

If the macro isn't defined, then you will get a compiler error because the symbol is not recognized.

Lundin
  • 195,001
  • 40
  • 254
  • 396
0
#if 0 // 0/1
#define DEBUG_PRINT printf("%s", "Testing")
#else
#define DEBUG_PRINT printf("%s")
#endif

So when "if 0" it'll do nothing and when "if 1" it'll execute the defined macro.

Sam Keith
  • 97
  • 4
  • 15