Appearance
[中文版本](/posts/c教程/zh-CN/06-编译与构建/053-Conditional Compilation (if, ifdef, ifndef))
Conditional compilation lets you control which code gets compiled, not at runtime, but at compile time. This is how C programs adapt to different operating systems, architectures, or build configurations without changing source files manually.
Think of it as logic for the compiler’s eyes only.
Step 1. Why Conditional Compilation Exists
Large C programs often need to handle differences such as:
- Platform (Windows, Linux, macOS, embedded)
- Compiler (gcc, clang, MSVC)
- Debug vs release builds
- Optional features or experimental modules
Instead of maintaining multiple versions of the same file, you can use conditional directives to selectively include or exclude code.
Step 2. The Core Directives
| Directive | Purpose |
|---|---|
#if | Compile code if expression is true |
#ifdef | Compile if macro is defined |
#ifndef | Compile if macro is not defined |
#else | Alternate block |
#elif | Else-if for preprocessor |
#endif | Marks the end of a conditional block |
These work only during preprocessing, before compilation starts.
Tiny Code: Platform-Specific Compilation
#include <stdio.h>
int main(void) {
#ifdef _WIN32
printf("Running on Windows\n");
#elif __linux__
printf("Running on Linux\n");
#elif __APPLE__
printf("Running on macOS\n");
#else
printf("Unknown platform\n");
#endif
return 0;
}Compile and run on your system. The output will depend on which predefined macros your compiler sets automatically.
Step 3. Enabling and Disabling Features
You can define flags at compile time with-D:
gcc -DDEBUG log.c -o logIn your code:
#ifdef DEBUG
printf("Debug mode: extra checks enabled\n");
#endifNo recompilation needed to switch, just re-rungcc with or without-DDEBUG.
You can also assign values:
gcc -DVERSION=2 main.c -o mainThen:
#if VERSION >= 2
printf("New feature enabled!\n");
#endifStep 4. Guarding Code with #ifndef
This is one of the most common idioms in C headers:
#ifndef CONFIG_H
#define CONFIG_H
#define MAX_CLIENTS 100
#define TIMEOUT_MS 3000
#endifIt ensures that ifconfig.h is included multiple times, it only gets processed once. Every header in the C standard library uses this pattern.
Step 5. Excluding Experimental Code
#define ENABLE_EXPERIMENTAL 0
#if ENABLE_EXPERIMENTAL
void experimental_feature() {
printf("Running experimental feature\n");
}
#endifIfENABLE_EXPERIMENTAL is set to0, this code is completely removed before compilation, it doesn’t even exist in the object file.
Tiny Code: Debug Mode Example
#include <stdio.h>
#define DEBUG_MODE 1
void compute(int x) {
#if DEBUG_MODE
printf("[DEBUG] compute() called with x=%d\n", x);
#endif
printf("Result: %d\n", x * x);
}
int main(void) {
compute(5);
return 0;
}Output whenDEBUG_MODE is 1:
[DEBUG] compute() called with x=5
Result: 25SetDEBUG_MODE to 0, recompile, and the[DEBUG] message disappears entirely.
Step 6. Using #elif and #else
#define OS 2
#if OS == 1
#define OS_NAME "Windows"
#elif OS == 2
#define OS_NAME "Linux"
#else
#define OS_NAME "Unknown"
#endif
int main(void) {
printf("OS: %s\n", OS_NAME);
return 0;
}Output:
OS: LinuxStep 7. Combining with Logical Operators
You can use&&,||, and! in preprocessor conditions.
#if defined(DEBUG) && !defined(RELEASE)
printf("Debug build only\n");
#endifYou can even use numeric comparisons:
#if VERSION >= 3
printf("Version 3+ detected\n");
#endifStep 8. Forcing Compilation Errors
Sometimes you want to stop compilation if a required macro is missing:
#ifndef API_KEY
#error "API_KEY not defined! Please compile with -DAPI_KEY=your_key"
#endifThis is useful for configuration validation at build time.
Step 9. Compiler-Specific Macros
Compilers automatically define macros to identify themselves and the environment.
| Macro | Meaning |
|---|---|
__GNUC__ | Defined by GCC |
__clang__ | Defined by Clang |
_MSC_VER | Defined by MSVC |
__x86_64__ | 64-bit architecture |
__arm__,__aarch64__ | ARM architectures |
__STDC__ | Conforms to ANSI C standard |
You can use these to write portable, adaptive code:
#ifdef __clang__
printf("Compiled with Clang\n");
#elif defined(__GNUC__)
printf("Compiled with GCC\n");
#endifTiny Code: Portable Sleep Function
#include <stdio.h>
#ifdef _WIN32
#include <windows.h>
#define SLEEP(ms) Sleep(ms)
#else
#include <unistd.h>
#define SLEEP(ms) usleep((ms) * 1000)
#endif
int main(void) {
printf("Waiting...\n");
SLEEP(1000);
printf("Done!\n");
return 0;
}This compiles cleanly on both Windows and Linux with no code changes.
Why It Matters
Conditional compilation makes your C code:
- Portable, same code runs on multiple systems
- Configurable, features can be toggled at build time
- Maintainable, no need for multiple codebases
- Efficient, excluded code doesn’t even enter the binary
In system software and embedded development, this is indispensable.
Try It Yourself
- Write a program that prints a different greeting depending on the OS.
- Use
#ifand#errorto enforce that only one ofDEBUGorRELEASEcan be defined. - Write a header that defines constants for different CPU architectures.
- Add a feature flag (
ENABLE_LOGGING) that can be turned on/off viagcc -D. - Use
#ifdefand#ifndefto create a lightweight build-time configuration system.
In the next section, you’ll take the next step toward clean, maintainable C code by learning about inline functions and header hygiene, modern, safer replacements for many macro patterns.
