Why should #ifdef be avoided in .c files?

(Somewhat off the asked question)

I saw a tip once suggesting the use of #if(n)def/#endif blocks for use in debugging/isolating code instead of commenting.

It was suggested to help avoid situations in which the section to be commented already had documentation comments and a solution like the following would have to be implemented:

/* <-- begin debug cmnt   if (condition) /* comment */
/* <-- restart debug cmnt {
                              ....
                          }
*/ <-- end debug cmnt

Instead, this would be:

#ifdef IS_DEBUGGED_SECTION_X

                if (condition) /* comment */
                {
                    ....
                }
#endif

Seemed like a neat idea to me. Wish I could remember the source so I could link it :(


  1. Because then when you do search results you don't know if the code is in or out without reading it.

  2. Because they should be used for OS/Platform dependencies, and therefore that kind of code should be in files like io_win.c or io_macos.c


Hard to maintain. Better use interfaces to abstract platform specific code than abusing conditional compilation by scattering #ifdefs all over your implementation.

E.g.

void foo() {
#ifdef WIN32
   // do Windows stuff
#else
   // do Posix stuff
#endif
   // do general stuff
}

Is not nice. Instead have files foo_w32.c and foo_psx.c with

foo_w32.c:

void foo() {
    // windows implementation
}

foo_psx.c:

void foo() {
    // posix implementation
}

foo.h:

void foo();  // common interface

Then have 2 makefiles1: Makefile.win, Makefile.psx, with each compiling the appropriate .c file and linking against the right object.

Minor amendment:

If foo()'s implementation depends on some code that appears in all platforms, E.g. common_stuff()2, simply call that in your foo() implementations.

E.g.

common.h:

void common_stuff();  // May be implemented in common.c, or maybe has multiple
                      // implementations in common_{A, B, ...} for platforms
                      // { A, B, ... }. Irrelevant.

foo_{w32, psx}.c:

void foo()  {  // Win32/Posix implementation
   // Stuff
   ...
   if (bar) {
     common_stuff();
   }
}

While you may be repeating a function call to common_stuff(), you can't parameterize your definition of foo() per platform unless it follows a very specific pattern. Generally, platform differences require completely different implementations and don't follow such patterns.


  1. Makefiles are used here illustratively. Your build system may not use make at all, such as if you use Visual Studio, CMake, Scons, etc.
  2. Even if common_stuff() actually has multiple implementations, varying per platform.