Conditional Compilation
Conditional compilation lets you include or exclude blocks of source code before the compiler ever sees them, based on conditions the preprocessor can evaluate at build time. This is how the same codebase can compile differently for different platforms, build configurations, or feature sets.
The Directive Family
Directive | Meaning |
|---|---|
| True if |
| True if |
| True if the constant integer expression |
| Like |
| Fallback branch if none of the above matched |
| Closes the conditional block |
#include <stdio.h>
#define VERSION 2
int main(void)
{
#if VERSION == 1
printf("Running version 1\n");
#elif VERSION == 2
printf("Running version 2\n");
#else
printf("Unknown version\n");
#endif
return 0;
}Common Uses
Platform-specific code — compile a different code path per operating system or compiler
Debug-only code — logging, assertions, or extra checks that should not exist in a release build
Feature flags — ship optional functionality that can be toggled on or off at compile time
#ifdef _WIN32
#include <windows.h>
#elif defined(__linux__)
#include <unistd.h>
#else
#include <unknown_platform.h>
#endif#if defined(X) vs #ifdef X
#ifdef X and #if defined(X) are equivalent — both test whether X has been defined. The #if defined(...) form has one practical advantage: because it is a normal expression, it composes with && and || to test several macros in a single condition, which plain #ifdef cannot do.
#if defined(DEBUG) && !defined(NDEBUG)
#define LOG(msg) printf("[DEBUG] %s\n", msg)
#else
#define LOG(msg) /* expands to nothing in release builds */
#endif
int main(void)
{
LOG("starting up"); /* real printf call in debug, no-op in release */
return 0;
}In a release build (where DEBUG is not defined), the LOG(...) macro expands to nothing at all — the call site simply disappears, with zero runtime cost. This pattern is extremely common for debug logging, assertions, and instrumentation that should never ship in production binaries.