Not a big fan of #ifdef or #ifndef


I am not a big fan of the C/C++ preprocessor directives
#ifdef
or
#ifndef.
I am not denying that they certainly have their place and usage in the language.
I’ll first write about where I think they are useful and then about the situations
where I feel they are not.
#ifndef is very useful for preventing multiple
inclusions of a header which will cause redefinition errors.

#ifndef __FOO_H__ // only proceed if the token is not yet defined
#define __FOO_H__ // define the token so that subsequent includes have it defined

[...contents of the header...]

#endif // __FOO_H__, end for #ifndef statement

Of course, you can use
#pragma once to do the same thing, but that is a
Microsoft specific extension to the language so not everyone will use it. This
usage is error prone though. I can't count the number of times that I copied one
header to create another header in the same project and forgot to change the
#ifndef token to a new token and couldn't
figure out why my new header wasn't being evaluated properly.

#ifdef is useful in a header to switch between
ANSI and wide versions of the function, such as this example from
winbase.h:

#ifdef UNICODE
#define CreateFile CreateFileW
#else
#define CreateFile CreateFileA
#endif // !UNICODE

Now, the reason I don't like these 2 directives is that they behave differently
then
#if token
or
#if !token.
#if token evaluates the value of
token.
If
token is not defined, it defaults to zero and the
#if token phrases evaluates to zero. On the other hand,
#ifdef token evaluates to token to see if it is
defined, regardless of the value of
token. That means that the following code


#define FOO 0x0

#ifdef FOO
printf("FOO!");
#else
printf("NOT FOO!");
#endif

will print out "FOO!" even though FOO evaluates to zero. As I have written previously
in my blog and in the newsgroups, I am big on maintainance of the code I write
because the code I write will have to be maintained for years and its connection
to me will eventually be lost by the developer who inherits it. What I don't like
about
#ifdef/ifndef
in a source file (vs the header file examples above) is that at first glance, I
mentally evaluate a
#ifdef
as a
#if
and more often then not, pick the wrong result when reviewing the code. Furthermore,
everytime I see
#ifndef
I need to spend a minute or two staring at the entire statement to figure out
what is going on, it is not immediately apparent to me what it is doing. Anytime
I have to stare at something to figure out what it does, it is an indicator to me
that others will probably do the same and it will eventually be a problem spot
in the code in the future.

A lot of you may not agree with me and I am fine with that ;). Please let me
know why though.

Comments (12)

  1. Rosyna says:

    GCC "supports" #import instead of #include. #import is equiv to #pragma once. Personally I find it depressing that preprocessors can create recursive include loops.

  2. #import also exists in the MSFT compiler, but I think the semantics are quite different ;).  In the MSFT compiler, you can use #import to suck in a tlb file with having to generate a header for it first.  

    I totally agree with the recursive nature of the C preprocessor.  It certainly feels like a workaround on top of a workaround that evolved over time to get things to compile.  Oh well, it is what it is ;).

  3. StewartT says:

    Personally, I would like to see your second example stricken from the record and replaced with something like

    #ifdef __cplusplus
    inline HWND CreateWindow( ….. ) {
    #ifdef _UNICODE
       return CreateWindowW(…..);
    #else
       return CreateWindowA(…..);
    #endif
    }
    #else
    … SAME …
    #endif
    [Actually you would need FORCEINLINE HWND CreateWindow()…inline is not “strong” of a keyword in my experience.  This is not probably not going to happen though.  The man hours required to make this change is probably greater then the benefit from that work…and then there is fear of code not compiling using the new style.]

    The number of times I have been caught out by a DLL interface which really doesn’t care whether you have defined _UNICODE or not not working because a class in a namespace has a member function called GetMessage (not an unreasonable name for a member function) and the library builder and client had different ideas about _UNICODE is annoying.

    Probably not the right place to vent this particular irritation, but you mentioned it 🙂

    [Fair enough, I understand your pain, I have been hit by it before.]

  4. gedoe says:

    #if X is just not thesame as
    #ifdef X

    [that’s what I said ;)]

    #ifdef is a shorthand for #if defined(X)

    I like to see the #if defined(X) iso #ifdef bu I am not against a
    defined test as opposed to a value check (you could see it as a NULL check)
    [True.  i would prefer !if defined(X) over #ifdef any day]

    on another note I dislike the #if X
    if there is no value check behind it Please please tell me what you are checking for, if you just want to know if the value exists use the #ifdef (or better #if defined() ) Otherwise tell me the value you are looking for
    [Well, this falls into style guidelines for if ()’s as a whole.  For instance, some people (myself included) don’t tolerate if (pValue), but would rather see if (pValue != NULL)]

  5. DrunkCod says:

    The whole #ifdef _UNICODE gunk has been bothering me with the windows headers since the really early days since they always find intresting way to completly screw your code around.

    Who at MS dreamt up the idea that replacing functionnames like that was a sane idea? Given that many of the names also are quite generic?

    [I wasn’t here for the initial design decision to do this, but I would assume it was to make existing code written for ANSI versions of Windows (e.g. not NT) compile for the newly introducted NT (we are speaking early 90s here).  NT didn’t have huge market share so it was in the NT’s team best interest to make existing code compile.]

    Is there anyway to force windows headers to simply not try to guess what version I want and omit the stupid defines always having me specify what version I want?
    [Nope]


    Also when doing C++ my tip is to start building your own header and simply creating overloaded functions and using #undef to clean up the global macros.

    [#undef is useful but scary b/c you have to read the headers in the right order, otherwise you might think the token is something else!]

  6. see my comments inline with the other comments…

    d

  7. bruteforce says:

    Yeah I have been bitten myself once by the #ifdef UNICODE trick, when trying to develop an CString derived class called CPathFileString that would implement all PathFileXXX functions as member functions. You bet I was not happy 🙂

    Wouldn’t it work out the same if all the neutral names were declared as const function pointers like this:

    extern __declspec(selectany) LPSTR (WINAPI * const lstrcpy)(LPSTR, LPCSTR) = lstrcpyA;

    The function pointer is const but will the compiler/linker eliminate all the function pointer ‘variables’ thus inducing no additional memory overhead to my program? If it does, then this solution I think would be cool.

    What annoys me most about the UNICODE ifdef is that you have to do two of them. UNICODE (for windows stuff) and _UNICODE (for CRT stuff). Why are there two defines?

    In fact some windows headers do the following ‘trick’

    // syncronize UNICODE options

    #if defined(_UNICODE) && !defined(UNICODE)

           #define UNICODE

    #endif

    #if defined(UNICODE) && !defined(_UNICODE)

           #define _UNICODE

    #endif

    Anyway going back to the generic #ifdef issue, now with VS2005 you get all #ifdefs evaluated by the IDE and the code that gets ifdef’ed out is greyed. Totally cool!!! It even works pretty well for "ddkbuild.bat" solutions. You just have to set your defines in the sln property pages and you’re on.

  8. The VS feature is pretty neat.  I use visual slickedit as my editor and it does something similar.  

    The 2 different #defines are annoying, but they come from 2 separate organizations.  the CRT’s #define might even be dictated by ISO (just a wild guess).

    The __declspec(selectany) trick is pretty cool.  __declspec(selectany) didn’t exist when they created these pattern in the headers which is probably one reason it is not used.  As for the additional global constant, I don’t know…but a quick test app should tell, no?

    d

  9. bruteforce says:

    A quick look at the assembly output showed that even under various optimization options a global variable is always generated, even if you don’t use the function pointer at all in your program.

    So I guess that would be a technique to use in your precompiled header when you have a specific function name conflict (like GetMessage) and you insist on using the same name in your code.

  10. That’s interesting that the global is always generated even if it is unreferenced.  In my previous experience with trying to create some constant globals for "type info" in KMDF, I could not convince the linker to leave the global in the image even though there were no references to it.

Skip to main content