17

People recommend #ifdef for conditional compilation by a wide margin. A search for #ifdef substantiates that its use is pervasive.

Yet #ifdef NAME (or equivalently #if defined(NAME) and related #ifndef NAME (and #if !defined(NAME)) have a severe flaw:

header.h

#ifndef IS_SPECIAL
#error You're not special enough
#endif

source.cpp

#include "header.h"

gcc -DIS_SPECIAL source.cpp

will pass, obviously, as will

source1.cpp

#define IS_SPECIAL 1
#include "header.h"

But, so will

source0.cpp

#define IS_SPECIAL 0
#include "header.h"

which is quite the wrong thing to do. And some C++ compilers, passed a file processed in C mode (due to extension or command-line option) effectively do #define __cplusplus 0. I have seen things break when

#ifdef __cplusplus
extern "C" {
#endif
/* ... */
#ifdef __cplusplus
}
#endif

was processed in C mode, where extern "C" is invalid syntax, because __cplusplus was in fact automatically defined to 0.

On the other hand, this behaves correctly for all compilers:

#if __cplusplus
extern "C" {
#endif
/* ... */
#if __cplusplus
}
#endif

Why do people still use #ifdef in this scenario? Are they simply unaware that #if works perfectly fine on undefined names? Or is there an actual disadvantage to #if vs #ifdef for conditional compilation?


Obviously, #ifdef does have valid uses, such as providing default values for configurable parameters:

#ifndef MAX_FILES
#define MAX_FILES 64
#endif

I'm only discussing the case of flag testing.

Community
  • 1
  • 1
Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • 10
    Why is it "quite the wrong thing to do"? The condition asks whether something is defined, and it's defined in both cases. One would expect the same results, no? – Joshua Taylor Jan 22 '14 at 02:01
  • 3
    @JoshuaTaylor: That means the compiler implemented the test correctly. It doesn't mean the user chose the correct test. See the discussion of `__cplusplus == 0` for why this is the wrong test. Or do you think that changing a setting from `1` to `0` shouldn't turn it off? – Ben Voigt Jan 22 '14 at 02:03
  • 3
    To downvoter: I'm curious what you think makes this question ill-researched? Is it because I didn't quote sections of the Standard that deal with `#if` and undefined macros? Because you don't think the answer is of practical value? Because the question is badly written and cannot be understood by normal C and C++ programmers? I'm happy to try to improve it if you give feedback – Ben Voigt Jan 22 '14 at 02:06
  • 2
    I'm not the downvote but I was tempted to until I got to the end. The question starts out with a mild insult in the title for people who like `#ifdef`, and then it's not even clear what the "better" alternative is until nearly the end. For a while I thought that you wanted people to use `#ifndef`. – Adam Jan 22 '14 at 02:13
  • @Adam: Thank you for pointing out that I lacked an example of what I consider the correct way. Just added. It's still toward the end, however. – Ben Voigt Jan 22 '14 at 02:15
  • 2
    @Joshua: As far as "One would expect the same results, no?" I have to say that (to take an example from autoconf) no, I do not expect `#define HAVE_READLINE 0` to do the same thing as `#define HAVE_READLINE 1`. – Ben Voigt Jan 22 '14 at 02:20
  • 1
    @CloseVoter: This question is not intended to be opinion-based. As far as I can tell, people use `#ifdef` for flag testing only because of personal preference. I'm asking whether there is any case in which it is objectively better. – Ben Voigt Jan 22 '14 at 02:23
  • 3
    The n1570 C draft standard, section 6.10.8 (Predefined macro names), clause 3 states "The implementation shall not predefine the macro `__cplusplus`, nor shall it define it in any standard header." A compiler in C mode defining it anyways is broken, so this particular example isn't a very strong argument for `#if` over `#ifndef`. – Iwillnotexist Idonotexist Jan 22 '14 at 02:23
  • @BenVoigt So its a question of whether people should be using a macro's existence as a flag or the macro's value as a flag? – BWG Jan 22 '14 at 02:23
  • 1
    @BWG: Yes, that's right. – Ben Voigt Jan 22 '14 at 02:24
  • 2
    @IwillnotexistIdonotexist: It's not broken, it conforms to C89, C90, **and C99**. There are still pre-C11 compilers around, and portable code shouldn't gratuitously break. – Ben Voigt Jan 22 '14 at 02:27
  • This is an example of the adage: There is more than one way to skin a cat. As this occurs at the preprocessor, there is no performance gain for either method, and neither is really better than the other. Hence, it is primarily opinion-based. – Zac Howland Jan 22 '14 at 02:45
  • The advantage of `#ifdef` rests in that there is only one level of logic involved: defined/undefined. On the other hand, `#if` admits two layers of logic: defined/undefined and zero/nonzero, when only one was required for conditional compilation. `#if` then conflates two of the three cases (undefined and defined-but-zero) together in your proposed usage, which may surprise some. The principle of least astonishment and Occam's Razor would have you prefer the simplest, best-understood construction that is still good enough, but no simpler. – Iwillnotexist Idonotexist Jan 22 '14 at 02:55
  • `#define THING` followed by `#if THING` yields unexpected results. – Raymond Chen Jan 22 '14 at 03:15
  • 1
    @iwillnot I would say that there are always two levels of logic involved and #ifdef ignores one of them. Do you really claim that conflating defined-but-zero with undefined is more confusing than conflating defined-but-zero with defined-and-one? – Ben Voigt Jan 22 '14 at 03:17
  • 1
    @Raymond It does not fail silently at least, unlike #ifdef – Ben Voigt Jan 22 '14 at 03:25
  • Some semi-standard C compilers used to define `__STDC__` as 0. Since they weren't compliant, there was nothing technically wrong, but they were a pain to use. If you have to use retrograde buggy compilers, you have to take special precautions. For most people, most of the time, working with `#if defined` or its equivalents is perfectly reasonable. If people insist on breaking the compilation by defining macros incorrectly, people will break the compilation — there's not much you can do to stop idiots from being idiots (though many have tried, few have succeeded; idiots are far too clever). – Jonathan Leffler Jan 22 '14 at 04:12
  • @jonathan Yes but is it "being an idiot" to change 1 to 0 in `#define USE_SSL 1` ? – Ben Voigt Jan 22 '14 at 04:15
  • If you don't know what the effect of making that change is going to be, then yes. Macros are designed to be used in one of two ways; by presence/absence testing with `defined`, or by being used as a value. If a macro is designed for use as a presence/absence test, then changing the value and expecting different behaviour is silly; that is not what the macro is designed for, and making a change from 1 to 0 or any other value will not make the slightest odds, and the person who changes it expecting a difference doesn't understand the code they're trying to modify/influence. – Jonathan Leffler Jan 22 '14 at 04:27
  • I use plain `#if`s exactly for this reason. If the tested macro is undefined, the preprocessor interprets it as falsy so everything works fine. The only place I found there's a difference between if and ifdef is in header double-inclusion guards with gcc. gcc optimizes out file reads if it sees an `#ifdef` guard, but it doesn't work (isn't optimized out) with `#if`. – Petr Skocik Oct 08 '17 at 17:48

2 Answers2

3

Why do people still use #ifdef in this scenario?

Personal opinion: it's marginally easier to control from the command line. I prefer -DOPTION over -DOPTION=1.

Also, existence of a name is clearly binary. I don't have to be able to handle {0, non-zero, undefined}.

Are they simply unaware that #if works perfectly fine on undefined names?

I wasn't aware. What are the semantics of this? Is an undefined name assumed to be 0? Do I want to have to explain that to the guy who barely understands the preprocessor to begin with?

Or is there an actual disadvantage to #if vs #ifdef for conditional compilation?

To me, the binary nature of #ifdef/#ifndef of name existence is a clarity benefit. Also, my primary usage of either construct is for include guards. That pattern is cleanest with #ifndef.

Adam
  • 16,808
  • 7
  • 52
  • 98
  • 4
    There's no difference between `-DOPTION` and `-DOPTION=1` (for any preprocessor I've ever used). Both define a macro `OPTION` with the value `1`. – Ben Voigt Jan 22 '14 at 02:24
  • 1
    For C++, 16.1p4 defines the semantics of `#if` on an undefined name, as follows: "After all replacements due to macro expansion and the `defined` unary operator have been performed, all remaining identifiers and keywords, except for true and false, are replaced with the pp-number 0, and then each preprocessing token is converted into a token." – Ben Voigt Jan 22 '14 at 02:30
  • @BenVoigt That all preprocessors of interest to you define a macro defined with `-D` to `1` is no guarantee it will remain that way. From above I see you are concerned with the portability of C89 code to various old compilers, but you may also be concerned with the portability of your code to various shell scripts and preprocessors. – Iwillnotexist Idonotexist Jan 22 '14 at 02:45
2

I cannot speak to why people in general prefer #ifdef over #if, but I can at least say why I do. Based on introspection just now (since you asked -- I've never considered it explicitly before), there are 2 reasons:

1) I prefer my macros (which I try to use sparingly) to have the most straightforward semantics as possible, and correspondingly as "type free" as possible. I assume that macros, if they have any type at all, are either "type free functions" (note: here I would strongly prefer templates, but there are times for everything...) or basically just boolean flags. Hence, even assigning a value of 1 to a macro is stretching it for me. (For example, what should it mean if you have #define _cplusplus 2? Should that be different in any way than 1?)

2) This last bit about them being "flags" goes along with the fact that I mostly use these for things I specify on the command line (or in the IDE) as conditional compilation flags. Indeed, on my software team, when we're writing C++, we're basically "prohibited" from using macros for anything else. And even in the conditional compilation case, we try to avoid them if we can solve the problem some other way (such as via modularity).

Both of these reasons relate to that same underlying assumption that macro use is to be avoided as much as possible (in C++) and so should not need the complexities of types or opaque semantics. If you don't make this assumption (and it's less common when programming in C, I know), then that changes things such that I imagine your points about #if might hold more sway.

Turix
  • 4,470
  • 2
  • 19
  • 27
  • FWIW, when you define a flag on the command-line using `-D` and don't specify any particular value, it gets the value `1`. – Ben Voigt Jan 22 '14 at 02:34
  • @BenVoigt I understand that. It's still about the difference between *flags* vs. *values*. – Turix Jan 22 '14 at 02:34
  • As you say yourself, flags are boolean. Either true or false. Clearly `1` is true, and clearly (undefined) is false. I think most people also believe that `0` is false, but to `#ifdef` it's not. – Ben Voigt Jan 22 '14 at 02:36
  • @BenVoigt Often, true is `-1`. (Btw, I am very strict about not conflating integers with booleans in any of my code, requiring a cast even to treat `1` as `true`, but I know others are not.) So then you have the case where you might allow for different behaviours when it's `1` or when it's `-1`. Don't get me wrong, I do see your point though. I believe if I programmed in C more, I would use `#if` more too. – Turix Jan 22 '14 at 02:39
  • @BenVoigt 'undefined' is NOT clearly false. Maybe here it's defined to be, but I can't think of another context where that's true. Usually 'undefined' is a third state, especially when you consider that people use multiple languages. – Adam Jan 22 '14 at 02:39
  • @Turix: Did I say that `-1` is false? I simply said that `1` is true. – Ben Voigt Jan 22 '14 at 02:40
  • @Adam: People who use `#ifdef` obviously intend that (undefined) is false. `#if` treats it the same way. The difference is in how they treat `0`. – Ben Voigt Jan 22 '14 at 02:41
  • @BenVoigt I know. But if `-1` can also be true, then you have the possibility that you could have things like `#if CVAR > 0` ... `#else` ... `#endif`, and (for me) that's adding too much semantic "possibility" to a flag. – Turix Jan 22 '14 at 02:42
  • 2
    "For example, what should it mean if you have `#define _cplusplus 2`? Should that be different in any way than 1?" — `__cplusplus` with two underscores expands to the date of the edition of C++ you are using. Pre-standard compilers, and GCC up to recently, defined it as 1 but later compilers define it as `199711L` or `201103L`. Just FYI… – Potatoswatter Jan 22 '14 at 03:51