12

When is doing conditional compilation a good idea and when is it a horribly bad idea?

By conditional compile I mean using #ifdefs to only compile certain bits of code in certain conditions. The #defineds themselves may be in either a common header file or introduced via the -D compiler directive.

aschepler
  • 70,891
  • 9
  • 107
  • 161
doron
  • 27,972
  • 12
  • 65
  • 103
  • 1
    This question is too unspecific, and in my opinion has no definite answer. Can you provide a specific scenario in which you are thinking of using conditional compilation? – Björn Pollex Dec 17 '10 at 10:40

8 Answers8

8

The good ideas:

  • header guards (you can't do much better for portability)
  • conditional implementation (juggling with platform differences)
  • debug specific checks (asserts, etc...)
  • per suggestion: extern "C" { and } so that the same headers may be used by the C++ implementation and by the C clients of the API

The bad idea:

  • changing the API between compile flags, since it forces the client to changes its uses with the same compile flags... urk!
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • 2
    I don't know if the second point in your "good ideas" section is really valid any more, given portable libraries such as boost/APR etc. I should imagine there are very few corner cases where there is something platform specific that needs to be handled within such a block. I think in this particular case, you are better off relying on the Makefiles to build appropriate implementations for platform rather than conditional compilation of blocks within the same file... – Nim Dec 17 '10 at 10:56
  • 3
    One more good idea: to conditionally compile `extern "C" {` in header files intended for exposing C APIs to C++. The bad idea should be "everything else". – JeremyP Dec 17 '10 at 10:59
  • @Nim: I agree, I am following the Clang project at the moment, which includes Howard Hinnant's libc++ so I am in a pretty low-level mindset :) @JeremyP: noted thanks, as for "everything else", although it's tempting, I prefer avoiding sweeping generalizations. – Matthieu M. Dec 17 '10 at 13:59
  • Nim: boost is simply too fat to fly, but even introducing APR as a dependency can often be unreasonable when all you really needed was a couple of ifdefs. On the other hand, ifdefs are simply bad if you never actually end up running the code in one of the conditionals. But that's not unique to ifdefs of normal conditional code. – kusma Dec 17 '10 at 14:14
  • 1
    @kusma, the problem is that the "couple of ifdefs" doesn't necessarily stay a couple though does it really? – Nim Dec 17 '10 at 23:01
  • @Nim: Actually, it often does if you maintain your code in a sane way. – kusma Dec 20 '10 at 15:53
3

Don't put ifdef in your code.
It makes it really hard to read and understand. Please make the code as easy to read as possable for the maintainer (he knows where you live and owns an Axe).

Hide the conditional code in separate functions and use the ifdef to define what functions are being used.

DONT use the else part to make a definition. If you are ding that you are saying one platform is unique and all others are the same. This is unlikely, what is more likely is that you know what happens on a couple of platforms but you should use the #else section to stick a #error so when it is ported to a new platform a developer has to explicitly fix the condition for his platform.

x.h

#if defined(WINDOWS)
#define   MyPlatfromSleepSeconds(x)  sleep(x * 1000)
#elif defined (UNIX)
#define   MyPlatfromSleepSeconds(x)  Sleep(x)
#else
#error "Please define appropriate sleep for your platform"
#endif

Don;t be tempted to expand a macro into multiple lines of code. That leads to madness.

p.h

#if defined(SOLARIS_3_1_1)
#define  DO_SOME_TASK(x,y)      doPartA(x);   \
                                doPartB(y);   \
                                couple(x,y)
#elif defined(WINDOWS)
#define  DO_SOME_TASK(x,y)      doAndCouple(x,y)
#else
#error "Please define appropriate DO_SOME_TASK for your platform"
#endif

If you develop the code on windows then test on solaris 3_1_1 later you may find unexpected bugs when people do things like:

int loop;
for(loop = 0;loop < 10;++loop)
    DO_SOME_TASK(loop,loop);             // Windows works fine()
                                         // Solaras. Only doPartA() is in the loop.
                                         //          The other statements are done when the loop finishes 



 
Community
  • 1
  • 1
Martin York
  • 257,169
  • 86
  • 333
  • 562
  • x.h: just for correctness: API mixed up: unix knows sleep(seconds), Win Sleep(ms) (I'm sure Loki knows, just a slip of the pen...) – Aconcagua Sep 02 '15 at 13:34
  • p.h: just want to annotate: to be more precise: having multiple statements within the same macro (keeping everything on one single line does not change anything); on the other hand: one CAN do such things, if one does them RIGHT (e. g. by wrapping the statements in a do { /*...*/} while(false) statement). Anyway, I don't want to propagate this, other means (like function inlining) are more appropriate in most of the cases. – Aconcagua Sep 02 '15 at 13:44
  • I would disagree with your "always #error". For example with sleep the select() function can be abused to do a short sleep on all those *including the above*, However, the specific variations are better so a "#warning" is probably a reasonable requirement in that sort of case. – user3710044 Jul 26 '23 at 10:46
2

Basically, you should try to keep the amount of code that is conditionally compiled to a minimum, because you should be trying to test all that and having lots of conditions makes that more difficult. It also reduces the readability of the code; conditionally compiling whole files is clearer, e.g., by putting platform-specific code in a separate file for each platform and having that all have the same API from the perspective of the rest of the problem. Also try to avoid using it in function headers; again, that's because that's a place where it is particularly confusing.

But that's not to say that you should never use conditional compilation. Just try to keep it short and minimal. (Where I can, I use conditional compilation to control the definitions of other macros which are then just used in the rest of the code; that seems to be clearer to me at least.)

Donal Fellows
  • 133,037
  • 18
  • 149
  • 215
1

It's a bad idea whenever you don't know what you're doing. It can be a good idea when you're effectively solving an issue this way :).

The way you describe conditional compiling, include guards are part of it. It's not only a good idea to use it. It's a way to avoid compilation errors.

For me, conditional compiling is also a way to target multiple compilers and operating systems. I'm involved in a lib that's supposed to be compileable on Windows XP and newer, 32 or 64 bit, using MinGW and Visual C++, on Linux 32 and 64 bit using gcc/g++ and on MacOS using I-don't-know-what (I'm not maintaining that, but I assume it's a gcc port). Without the preprocessor conditions, it would be pretty much impossible to create a single source file that's compileable anywhere.

mingos
  • 23,778
  • 12
  • 70
  • 107
1

Another pragmatic use of conditional compiles is to "comment out" sections of code which contain standard "C" comments (i.e. /* */). Some compilers do not allow nesting of these comments, for example:

/* comment out block of code

.... code ....
/* This is a standard 
 * comment.
 */  ... oopos!  Some compilers try to compile code after this closing comment.
.... code ....

end of block of code*/

(As you can see in the syntax highlighting, StackOverflow does not nest comments.)

instead you can use#ifdef to get the right effect, for example:

#ifdef _NOT_DEFINED_

.... code ....
/* This is a standard 
 * comment.
 */  
.... code ....

#endif
Commodore63
  • 339
  • 1
  • 13
0

In the past if you wanted to produce truly portable code, you'd have to resort to some form of conditional compilation. With there being a proliferation of portable libraries (such as APR, boost etc.) this reason has little weight IMHO. If you are using conditional compilation simply compile out blocks of code that are not need for particular builds, you should really revisit your design - I should imagine that this would become a nightmare to maintain.

Having said all that, if you do need to use conditional compilation, I would hide as much as I can away from the main body of the code and limit to to very specific cases that are very well understood.

Nim
  • 33,299
  • 2
  • 62
  • 101
0

Good/justifiable uses are based on cost/benefit analysis. Obviously, people here are very conscious of the risks:

  • in linking objects that saw different versions of classes, functions etc.
  • in making code hard to understand, test and reason about

But, there are uses which often fall into the net-benefit category:

  • header guards
  • code customisations for distinct software "ecosystems", such as Linux versus Windows, Visual C++ versus GCC, CPU-specific optimisations, sometimes word size and endianness factors (though with C++ you can often determine these at compile via template hackery, but that may prove messier still) - abstracts away lower-level differences to provide a consistent API across those environments
  • interacting with existing code that uses preprocessor defines to select versions of APIs, standards, behaviours, thread safety, protocols etc. (sad but true)
  • compilation that may use optional features when available (think of GNU configure scripts and all the tests they perform on OS interfaces etc)
  • request that extra code be generated in a translation unit, such as adding main() for a standalone app versus without for a library
  • controlling code inclusion for distinct logical build modes such as debug and release
Tony Delroy
  • 102,968
  • 15
  • 177
  • 252
-4

It is always a bad idea. What it does is effectively create multiple versions of your source code, all of which need to be tested, which is a pain, to say the least. Unfortunately, like many bad things it is sometimes unavoidable. I use it in very small amounts when writing code that needs to be ported between Windows and Linux, but if I found myself doing it a lot, I would consider alternatives, such as having two separate development sub-trees.

unquiet mind
  • 1,082
  • 6
  • 11
  • I think you're being too general here. Conditional compilation is not ALWAYS a bad idea, but several of the common use-cases are. – kusma Dec 17 '10 at 10:42
  • I can't agree. It's like saying "pointers are bad because you can get screwed". Conditional compilation can be used right and then it doesn't hurt too much. – sharptooth Dec 17 '10 at 10:42
  • 3
    "create multiple versions..." it's not a fault of conditional compilation, if you **need** to create different versions of the code (like multi-platform code) then whatever way you write it you'll need to test multiple versions. – Yakov Galka Dec 17 '10 at 10:42
  • 1
    "two separate development trees" how is it better than conditional compilation? – Yakov Galka Dec 17 '10 at 10:44
  • @ybung It's better because we have tools (like VCS, diff etc) to handle multiple source trees - we don't have good tools to manage conditional compilation. – unquiet mind Dec 17 '10 at 10:50
  • 1
    @unquiet: you don't need to manage conditional compilation. With conditional compilation you write the "diff" in the source itself. If you move to multiple development trees **then** your maintenance becomes a headache, and **then** you need VCS and other tools to manage your duplicate source code. – Yakov Galka Dec 17 '10 at 11:12
  • Multiple source trees is a bad idea. If you find a bug in one place then you will need to edit multiple files to make the fix across all platforms. Keep the code localised so that it can be maintained at a single point. – Martin York Dec 17 '10 at 19:10