73

In my current codebase I see this following pattern:

#if SOMETHING_SUPPORTED+0 != 0
...
#endif

Unfortunately this is a very old codebase and nobody knows how and why it started. I think it started in C and it was slowly converted into C with classes and now it tends to C++

I can't see any obvious advantage of using previous construct instead of the "classic", but maybe I'm missing something:

#if SOMETHING_SUPPORTED
...
#endif

Do you know why would one use #if MACRO+0 != 0 instead of #if MACRO?

Mircea Ispas
  • 20,260
  • 32
  • 123
  • 211

4 Answers4

71

The clue here is that the code base is very old.

This trick likely exists because the code once had been ported to a compiler with some very old preprocessor which doesn't treat undefined macros as 0 in preprocessor #if conditionals.

That is to say, as of 1989 ANSI C it was standardized that if we have:

#if foo + bar - xyzzy

the directive is subject to macro replacement, so that if foo, bar or xyzzy are macros, they are replaced. Then any remaining identifiers which were not replaced are replaced with 0. So if foo is defined as 42, but bar and xyzzy are not defined at all, we get:

#if 42 + 0 - 0

and not, say, bad syntax:

#if 42 + -

or some other behavior, like diagnostics about bar not being defined.

On a preprocessor where undefined macros are treated as blanks, #if SOMETHING_SUPPORTED expands to just #if, which is then erroneous.

This is the only way in which this IDENT+0 trick makes any real sense. You simply wouldn't ever want to do this if you can rely on preprocessing being ISO C conforming.

The reason is that if SOMETHING_SUPPORTED is expected to have numeric values, it is erroneously scatter-brained to define it as simply a blank. You ideally want to detect when this has happened and stop the compilation with a diagnostic.

Secondly, if you do support such a scatter-brained usage, you almost certainly want an explicitly defined, but blank symbol to behave as if it had the value 1, not the value 0. Otherwise, you're creating a trap. Someone might do this on the compiler command line:

 -DSOMETHING_SUPPORTED=$SHELL_VAR  # oops, SHELL_VAR expanded to nothing

or in code:

 #define SOMETHING_SUPPORTED  /* oops, forgot "1" */

Nobody is going to add a #define or -D for a symbol with the intent of turning off the feature that it controls! The programmer who inserts a #define SOMETHING_SUPPORTED without the 1 will be surprised by the behavior of

 #if SOMETHING_SUPPORTED+0

which skips the material which was intended to be enabled.

This is why I suspect that few C programmers reading this have ever seen such a usage, and why I suspect that it's just a workaround for preprocessor behavior whose intended effect is to skip the block if SOMETHING_SUPPORTED is missing. The fact that it lays a "programmer trap" is just a side-effect of the workaround.

To work around such a preprocessor issue without creating a programmer trap is to have, somewhere early in the translation unit, this:

#ifndef SOMETHING_SUPPORTED
#define SOMETHING_SUPPORTED 0
#endif

and then elsewhere just use #if SOMETHING_SUPPORTED. Maybe that approach didn't occur to the original programmer, or perhaps that programmer thought that +0 trick was neat, and placed value on its self-containment.

Kaz
  • 55,781
  • 9
  • 100
  • 149
  • 1
    You're not wrong, but ... all Unixy compilers I know of treat `-DTHING` as shorthand for `-DTHING=1`; if you actually want the macro to expand to nothing you have to say `-DTHING=`. – zwol May 05 '16 at 18:26
  • 1
    @zwol Could happen by way of `-DFOO=$FOO_ON`, where the shell var expanded to nothing, rather than 0 or 1 as expected! (edited). – Kaz May 05 '16 at 18:31
  • @zwol Indeed. This is probably because build scripts are tailored to a single use case, operating on nearly the same inputs all the time, and so their logic is geared toward the expected behaviors of that use case. (Not unlike the throwaway scripts that people write for their own use, or maybe even worse). – Kaz May 05 '16 at 21:25
45

#if X+0 != 0 is different to #if X in the case where X is defined to empty (note: this is different to the case of X not being defined), e.g.:

#define X

#if X          // error
#if X+0 != 0   // no error; test fails

It is very common to define empty macros: project configuration may generate some common header that contains a bunch of lines #define USE_FOO, #define USE_BAR to enable features that the system supports, and so on.

The != 0 is redundant, the code could have just been #if X+0.


So, the benefit of using #if X+0 is that if X is defined as empty then compilation continues with the block being skipped, instead of triggering an error.

Whether this is a good idea is debatable, personally I would use #ifdef for boolean macros like USE_SOME_FEATURE, and #if for macros where the value might be a range of integers for example; and I would want to see an error if I accidentally use #if with something defined to empty.

M.M
  • 138,810
  • 21
  • 208
  • 365
  • 1
    A flag like `-DNDEBUG` will define `NDEBUG` to be `1`, not empty. – Dietrich Epp May 05 '16 at 11:21
  • @DietrichEpp you're right. `-DNEBUG=` would define it to empty – M.M May 05 '16 at 11:33
  • I've never seen an error from this. The `#if` just evaluates to false in my experience (which is still probably not desired). EDIT: I stand corrected, this indeed produces an error. Not sure what I'd seen in the past. Maybe it was compiler-specific. – Cameron May 05 '16 at 13:23
  • @M.M To remind: however, it is impossible to create the [general purpose emptiness detector macro](https://stackoverflow.com/q/65044384/1778275). Note: some "close-to" implementations exist. Here is [one](https://gustedt.gitlabpages.inria.fr/p99/p99-html/group__basic__list__operations_ga27a53661a1c739a0f9d1e70f8958e4bc.html) of them. – pmor Sep 15 '22 at 19:06
35

Let's make a table!

X       #if X     #if X+0 != 0
<undef> false     false
<empty> error     false
0       false     false
1       true      true
2       true      true
a       false     false
xyz     false     false
12a     error     error
12 a    error     error

So the only difference we've found (thanks to commenters) is the case where X is defined but has no value (like empty string). I've never seen the +0 != 0 variant before.

Community
  • 1
  • 1
John Zwinck
  • 239,568
  • 38
  • 324
  • 436
  • 1
    @M.M: I though so as well. I just test it. It works as expected. So I deleted my answer as wrong. – Martin York May 05 '16 at 10:34
  • @M.M: No it is not. http://stackoverflow.com/questions/1643820/why-no-warning-with-if-x-when-x-undefined – Dietrich Epp May 05 '16 at 10:38
  • @DietrichEpp that question is about X undefined, not X empty. I guess the table in this answer should rename "empty" to "undefined", and add a new row for "empty" – M.M May 05 '16 at 11:05
  • @LokiAstari maybe you did something wrong, [see here](https://godbolt.org/g/P75jjM) – M.M May 05 '16 at 11:05
  • You can find other mismatches if you abuse the operator precedence rules, [for example](http://ideone.com/fYULhV) with `#define X 1^2`. – Ilmari Karonen May 05 '16 at 15:33
7

It's another way of writing

 #if defined(MACRO) && MACRO != 0

The +0 is there to ensure the result is a number. If MACRO isn't defined it avoids the syntax error that would result from #if MACRO != 0.

Poor quality stuff, but don't mess with it unless you gotta.

user207421
  • 305,947
  • 44
  • 307
  • 483
  • #if defined(MACRO) && MACRO != 0 => false when macro is not defined #if MACRO+0 !=0 => false when macro is not defined, but for a different reason: 0+0 != 0 They have the same result, but they are not equivalent http://stackoverflow.com/questions/5085392/what-is-the-value-of-an-undefined-constant-used-in-if-c – Mircea Ispas May 05 '16 at 10:50
  • @Felics I am unable to ascribe any useful meaning to 'they have the same result but they are not equivalent'. I haven't said they *were* equivalent actually. Your point remains obscure. – user207421 May 05 '16 at 10:56
  • I wanted to point out that `#if MACRO+0 !=0` is not a different way of writing `#if defined(MACRO) && MACRO != 0`. Ex: `#define MACRO` would result in a build error for `#if defined(MACRO) && MACRO != 0` and would compile fine for `#if MACRO+0 !=0` – Mircea Ispas May 05 '16 at 11:02
  • 8
    This is incorrect: after `#define MACRO`, OP code compiles but your version doesn't – M.M May 05 '16 at 11:10