C Conditional Compilation, #ifdef, #ifndef & Include Guards Masterclass
Welcome to Phase 15 (Chapter 40): Conditional Compilation & Include Guards Masterclass! Conditional compilation lets you include or exclude entire blocks of C code based on preprocessor conditions. This is essential for cross-platform code, debug builds, feature flags, and preventing recursive header inclusion.
| Directive | Meaning |
|---|---|
#if EXPR | Include block if constant expression evaluates to non-zero. |
#ifdef NAME | Include block if macro NAME is defined. |
#ifndef NAME | Include block if macro NAME is NOT defined. |
#elif EXPR | Else-if branch for conditional compilation. |
#else | Fallback block if no prior condition matched. |
#endif | Closes any #if / #ifdef / #ifndef block. |
When multiple source files include the same header, the C preprocessor will paste its contents multiple times โ causing duplicate type definitions that fail to compile. Include guards prevent this:
Classic #ifndef Include Guard Pattern:
Every production header file MUST wrap its contents with:
#ifndef MY_HEADER_H โ Check if not already defined.
#define MY_HEADER_H โ Mark as defined on first inclusion.
... Header content ...
#endif /* MY_HEADER_H */ โ Close guard block.
Modern alternative: #pragma once โ Supported by all major compilers (GCC, Clang, MSVC). Simpler but technically non-standard (C standard does not mandate it).
#include <stdio.h>
/* Define DEBUG to enable verbose logging */
#define DEBUG 1
#if DEBUG
#define LOG(msg) printf("[DEBUG] %s (File:%s Line:%d)\n", (msg), __FILE__, __LINE__)
#else
#define LOG(msg) /* Empty: stripped in Release build */
#endif
/* Platform detection */
#if defined(_WIN32)
#define PLATFORM_NAME "Windows"
#elif defined(__linux__)
#define PLATFORM_NAME "Linux"
#elif defined(__APPLE__)
#define PLATFORM_NAME "macOS"
#else
#define PLATFORM_NAME "Unknown Platform"
#endif
int main(void) {
LOG("Program started");
printf("Running on: %s\n", PLATFORM_NAME);
int result = 42;
LOG("Computation complete");
printf("Result: %d\n", result);
return 0;
}Q1: What is the difference between #ifdef and #if defined()?
#if defined(X) can be combined with logical operators: #if defined(A) && !defined(B). #ifdef only tests a single macro name.
Q2: Should I use include guards or #pragma once?
Use #pragma once for new projects on GCC/Clang/MSVC. Use traditional include guards for maximum portability to embedded or exotic toolchains.
Q3: Can conditional compilation check numeric values?
Yes. #if VERSION >= 2 works if VERSION is a macro defined as an integer constant.
Q4: How do I pass macro definitions from the compiler command line?
Use the -D flag: gcc -DDEBUG=1 -o app app.c. This defines the DEBUG macro as 1 without modifying source files.
Q5: Can preprocessor conditionals be nested?
Yes, #if / #ifdef blocks can be nested as deeply as needed. Every nested block requires its own matching #endif.