Q&A
What is the #if directive in C?
Overview
C's preprocessor transforms source text before compilation. #if/#ifdef/#ifndef/#elif/#else/#endif let you selectively include code based on conditions evaluated entirely at preprocessing time. The compiler never sees the excluded branches - they're as if they don't exist in the translation unit. This is distinct from runtime if - the decision is made when you compile, not when you run.
graph TD
A["#if CONDITION"] --> B{Condition true at compile time?}
B -->|yes| C["Included in compiled code"]
B -->|no| D["Stripped out before compiler sees it"]
C & D --> E["#endif - end of conditional block"]
style C fill:#1e1b4b,color:#fff
Basic #if / #elif / #else / #endif
#define VERSION 2
#if VERSION == 1
printf("Running v1\n");
#elif VERSION == 2
printf("Running v2\n");
#else
printf("Unknown version\n");
#endif
The preprocessor evaluates the expression; only the matching branch is compiled. Expressions can use ==, !=, <, >, &&, ||, ! with integer constants and macros.
#ifdef and #ifndef (existence checks)
#ifdef DEBUG
printf("Debug value: %d\n", x); // only compiled when DEBUG is defined
#endif
// Equivalent to:
#if defined(DEBUG)
// ...
#endif
#ifdef MACRO - true if MACRO has been #defined (regardless of its value).
#ifndef MACRO - true if MACRO has NOT been defined.
Most Common Use 1: Include Guards (prevent double inclusion)
// myheader.h
#ifndef MYHEADER_H
#define MYHEADER_H
// ... header contents ...
#endif
The first time this header is included, MYHEADER_H is undefined → contents compiled → macro defined. Any subsequent #include of the same header finds MYHEADER_H already defined → contents skipped. This prevents redefinition errors. (Modern equivalent: #pragma once, widely supported.)
Most Common Use 2: Platform-Specific Code
#if defined(_WIN32)
#include <windows.h>
void sleep_ms(int ms) { Sleep(ms); }
#elif defined(__APPLE__)
#include <unistd.h>
void sleep_ms(int ms) { usleep(ms * 1000); }
#else
#include <unistd.h>
void sleep_ms(int ms) { usleep(ms * 1000); }
#endif
Each platform gets the right implementation; the others don't reach the compiler.
Most Common Use 3: Debug vs Release Builds
#ifdef DEBUG
#define LOG(msg) fprintf(stderr, "[DEBUG] %s:%d: %s\n", __FILE__, __LINE__, msg)
#else
#define LOG(msg) // empty macro in release - zero overhead
#endif
Compile with -DDEBUG (gcc/clang) to enable logging; production builds omit it entirely.
defined() Operator
#if defined(WINDOWS) && !defined(LEGACY_MODE)
// modern Windows code
#endif
defined(MACRO) is more flexible than #ifdef because it can be combined with other conditions in a single #if.
The Difference: #if vs runtime if
#if (preprocessor) |
if (runtime) |
|
|---|---|---|
| When | Before compilation | At program execution |
| Expression | Integer constants, macros | Any expression |
| Excluded code | Never compiled | Always compiled, not executed |
| Performance | Zero runtime cost | Branch prediction, code size |
| Debug build overhead | Zero | Possible |
Why This Matters in Interviews
C/C++ systems interviews use #if to test preprocessor knowledge. Key points: #if is evaluated at compile time (by the preprocessor, before the compiler); #ifdef/#ifndef check macro existence; the three major use cases are include guards, platform-specific code, and debug/release builds; and the conditional branches that don't match are not compiled at all (true dead-code elimination). Distinguishing it from runtime if is the core conceptual point.
Common Pitfalls
- Using
#if MACROwhen you mean#if defined(MACRO)- ifMACROis not defined,#if MACROtreats it as0(false), while#if defined(MACRO)tests for existence. - Forgetting
#endif- causes mysterious "unterminated conditional" errors, often in a different file. - Relying on
#ifdeffor complex feature flags in large codebases (consider feature-detection and runtime flags for maintainability). - Mixing up
#if 0(comment out a block) with//comments for multi-line blocks. - Using include guards with easily colliding names (
#ifndef UTIL_Hwhen you have multipleutil.hfiles in different directories).
References
- C preprocessor
#if: https://en.cppreference.com/w/c/preprocessor/conditional - GCC preprocessor manual: https://gcc.gnu.org/onlinedocs/cpp/index.html