Why do people use #ifdef for feature flag tests?
Asked Answered
P

2

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.

Pilpul answered 22/1, 2014 at 1:58 Comment(19)
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?Emotion
@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?Pilpul
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.Clarsach
@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.Pilpul
@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.Pilpul
@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.Pilpul
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.Annamariaannamarie
@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?Shantell
@BWG: Yes, that's right.Pilpul
@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.Pilpul
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.Mindimindless
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.Annamariaannamarie
#define THING followed by #if THING yields unexpected results.Behn
@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?Pilpul
@Raymond It does not fail silently at least, unlike #ifdefPilpul
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).Auston
@jonathan Yes but is it "being an idiot" to change 1 to 0 in #define USE_SSL 1 ?Pilpul
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.Auston
I use plain #ifs 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.Dantedanton
C
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.

Clarsach answered 22/1, 2014 at 2:23 Comment(3)
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.Pilpul
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."Pilpul
@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.Annamariaannamarie
D
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.

Discontinuous answered 22/1, 2014 at 2:32 Comment(9)
FWIW, when you define a flag on the command-line using -D and don't specify any particular value, it gets the value 1.Pilpul
@BenVoigt I understand that. It's still about the difference between flags vs. values.Discontinuous
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.Pilpul
@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.Discontinuous
@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.Clarsach
@Turix: Did I say that -1 is false? I simply said that 1 is true.Pilpul
@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.Pilpul
@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.Discontinuous
"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…Figuration

© 2022 - 2024 — McMap. All rights reserved.