README
Conditional Compilation in C
Table of Contents
- •Introduction
- •Basic Conditional Directives
- •#ifdef and #ifndef
- •#if, #elif, #else, #endif
- •defined Operator
- •Conditional Compilation Use Cases
- •Platform-Specific Code
- •Debug and Release Builds
- •Feature Toggles
- •Version-Specific Code
- •Best Practices
- •Common Patterns
Introduction
Conditional compilation is a powerful preprocessor feature that allows you to include or exclude portions of code from compilation based on certain conditions. This is evaluated at compile-time, not runtime, meaning excluded code is never compiled and doesn't exist in the final binary.
Why Use Conditional Compilation?
- •Platform Portability: Write code that compiles on different operating systems
- •Debug vs Release: Include debugging code only in development builds
- •Feature Flags: Enable/disable features without changing code
- •Hardware Adaptation: Optimize for different hardware architectures
- •API Versions: Support multiple versions of libraries
- •Code Organization: Manage large codebases with optional components
Preprocessor vs Runtime Conditionals
| Aspect | Preprocessor | Runtime |
|---|---|---|
| Evaluation | Compile-time | Runtime |
| Binary Size | Excluded code absent | All code present |
| Performance | Zero overhead | Condition check overhead |
| Flexibility | Fixed at compile | Can change during execution |
| Debugging | Can't debug excluded code | All code debuggable |
Basic Conditional Directives
The C preprocessor provides several directives for conditional compilation:
Complete List of Directives
| Directive | Purpose |
|---|---|
#if | If expression is true (non-zero) |
#ifdef | If macro is defined |
#ifndef | If macro is not defined |
#elif | Else if (another condition) |
#else | Else (no condition) |
#endif | End of conditional block |
#define | Define a macro |
#undef | Undefine a macro |
Basic Structure
#if condition
// Code compiled if condition is true
#elif another_condition
// Code compiled if another_condition is true
#else
// Code compiled if all conditions are false
#endif
Simple Example
#define DEBUG 1
#if DEBUG
printf("Debug mode is enabled\n");
#else
printf("Release mode\n");
#endif
#ifdef and #ifndef
These directives check whether a macro is defined, regardless of its value.
#ifdef Syntax
#ifdef MACRO_NAME
// Code compiled if MACRO_NAME is defined
#endif
#ifndef Syntax
#ifndef MACRO_NAME
// Code compiled if MACRO_NAME is NOT defined
#endif
Examples
// Debug printing - only if DEBUG is defined
#ifdef DEBUG
#define DEBUG_PRINT(msg) printf("[DEBUG] %s\n", msg)
#else
#define DEBUG_PRINT(msg) ((void)0)
#endif
// Default configuration if not provided
#ifndef MAX_BUFFER_SIZE
#define MAX_BUFFER_SIZE 1024
#endif
// Multiple conditions with #ifdef
#ifdef FEATURE_A
void feature_a_function(void) {
// Implementation
}
#endif
#ifdef FEATURE_B
void feature_b_function(void) {
// Implementation
}
#endif
Checking if Defined with Value
// These are different!
#define ENABLED 1
#define DISABLED 0
#define FEATURE // Defined but no value (empty)
#ifdef ENABLED // True - ENABLED is defined
#ifdef DISABLED // True - DISABLED is defined
#ifdef FEATURE // True - FEATURE is defined
#ifdef UNDEFINED // False - not defined
#if ENABLED // True - value is 1 (non-zero)
#if DISABLED // False - value is 0
#if FEATURE // Error or 0 - no value
#if, #elif, #else, #endif
The #if directive evaluates constant expressions at compile time.
Expression Operators
The following operators can be used in #if expressions:
| Operator | Description |
| ----------------------- | ------------------------- | ----------------- | ----------------- |
| ==, != | Equality comparison |
| <, >, <=, >= | Relational comparison |
| &&, | |, ! | Logical operators |
| +, -, *, /, % | Arithmetic operators |
| &, |, ^, ~ | Bitwise operators |
| <<, >> | Shift operators |
| defined() | Check if macro is defined |
Basic #if Examples
#define VERSION 2
#if VERSION == 1
printf("Version 1\n");
#elif VERSION == 2
printf("Version 2\n");
#elif VERSION >= 3
printf("Version 3 or later\n");
#else
printf("Unknown version\n");
#endif
Compound Conditions
#define MAJOR_VERSION 2
#define MINOR_VERSION 5
#define PLATFORM_LINUX 1
#if MAJOR_VERSION > 2 || (MAJOR_VERSION == 2 && MINOR_VERSION >= 5)
#define HAS_NEW_FEATURES 1
#endif
#if defined(PLATFORM_LINUX) && MAJOR_VERSION >= 2
#define LINUX_OPTIMIZED 1
#endif
Nested Conditionals
#define OS_WINDOWS 0
#define OS_LINUX 1
#define OS_MACOS 0
#define ARCH_X86 0
#define ARCH_X64 1
#if OS_LINUX
#if ARCH_X64
#define PLATFORM_STRING "Linux x64"
#elif ARCH_X86
#define PLATFORM_STRING "Linux x86"
#else
#define PLATFORM_STRING "Linux (unknown arch)"
#endif
#elif OS_WINDOWS
#if ARCH_X64
#define PLATFORM_STRING "Windows x64"
#else
#define PLATFORM_STRING "Windows x86"
#endif
#elif OS_MACOS
#define PLATFORM_STRING "macOS"
#else
#define PLATFORM_STRING "Unknown platform"
#endif
defined Operator
The defined operator checks if a macro is defined and can be used in #if expressions.
Syntax
#if defined(MACRO_NAME)
// or
#if defined MACRO_NAME
Equivalence
// These are equivalent:
#ifdef DEBUG
#if defined(DEBUG)
#if defined DEBUG
// These are equivalent:
#ifndef DEBUG
#if !defined(DEBUG)
#if !defined DEBUG
Advantage of defined()
The defined operator can be combined with other conditions:
// This requires defined() - can't do with #ifdef alone
#if defined(DEBUG) && defined(VERBOSE)
printf("Verbose debugging enabled\n");
#endif
// Complex condition
#if defined(WINDOWS) || defined(WIN32) || defined(_WIN32)
#define IS_WINDOWS 1
#endif
// Negation with other conditions
#if !defined(NDEBUG) && LEVEL > 2
#define ENABLE_EXTRA_CHECKS 1
#endif
Multiple Macro Checks
// Check for any of several macros
#if defined(__GNUC__) || defined(__clang__)
#define COMPILER_GCC_COMPATIBLE 1
#endif
// Check that all required macros are defined
#if defined(CONFIG_A) && defined(CONFIG_B) && defined(CONFIG_C)
#define FULL_CONFIG 1
#endif
// Feature detection pattern
#if defined(HAS_FEATURE_X) && HAS_FEATURE_X > 0
// Use feature X
#endif
Conditional Compilation Use Cases
Use Case 1: Including/Excluding Code Sections
// Expensive assertions only in debug mode
#ifdef DEBUG
#define ASSERT(cond) do { \
if (!(cond)) { \
fprintf(stderr, "Assertion failed: %s\n", #cond); \
fprintf(stderr, "File: %s, Line: %d\n", __FILE__, __LINE__); \
abort(); \
} \
} while(0)
#else
#define ASSERT(cond) ((void)0)
#endif
Use Case 2: Selecting Implementations
// Choose sorting algorithm at compile time
#if defined(USE_QUICKSORT)
#include "quicksort.h"
#define SORT(arr, n) quicksort(arr, n)
#elif defined(USE_MERGESORT)
#include "mergesort.h"
#define SORT(arr, n) mergesort(arr, n)
#else
#include "bubblesort.h"
#define SORT(arr, n) bubblesort(arr, n)
#endif
Use Case 3: Optional Dependencies
// Use OpenSSL if available, otherwise use built-in
#if defined(HAVE_OPENSSL)
#include <openssl/sha.h>
#define compute_hash(data, len) SHA256(data, len, NULL)
#else
#include "simple_hash.h"
#define compute_hash(data, len) simple_sha256(data, len)
#endif
Use Case 4: Compile-time Configuration
// Configurable buffer sizes
#ifndef INPUT_BUFFER_SIZE
#define INPUT_BUFFER_SIZE 4096
#endif
#ifndef OUTPUT_BUFFER_SIZE
#define OUTPUT_BUFFER_SIZE 8192
#endif
#ifndef MAX_CONNECTIONS
#define MAX_CONNECTIONS 100
#endif
// Array sizing at compile time
char input_buffer[INPUT_BUFFER_SIZE];
char output_buffer[OUTPUT_BUFFER_SIZE];
Platform-Specific Code
Operating System Detection
// Detect operating system
#if defined(_WIN32) || defined(_WIN64)
#define OS_WINDOWS 1
#define OS_NAME "Windows"
#elif defined(__linux__)
#define OS_LINUX 1
#define OS_NAME "Linux"
#elif defined(__APPLE__) && defined(__MACH__)
#define OS_MACOS 1
#define OS_NAME "macOS"
#elif defined(__FreeBSD__)
#define OS_FREEBSD 1
#define OS_NAME "FreeBSD"
#elif defined(__unix__)
#define OS_UNIX 1
#define OS_NAME "Unix"
#else
#define OS_UNKNOWN 1
#define OS_NAME "Unknown"
#endif
Architecture Detection
// Detect CPU architecture
#if defined(__x86_64__) || defined(_M_X64)
#define ARCH_X64 1
#define ARCH_NAME "x86_64"
#elif defined(__i386__) || defined(_M_IX86)
#define ARCH_X86 1
#define ARCH_NAME "x86"
#elif defined(__aarch64__) || defined(_M_ARM64)
#define ARCH_ARM64 1
#define ARCH_NAME "ARM64"
#elif defined(__arm__) || defined(_M_ARM)
#define ARCH_ARM 1
#define ARCH_NAME "ARM"
#else
#define ARCH_UNKNOWN 1
#define ARCH_NAME "Unknown"
#endif
Platform-Specific Includes
#ifdef OS_WINDOWS
#include <windows.h>
#define SLEEP_MS(ms) Sleep(ms)
#define PATH_SEPARATOR "\\"
#elif defined(OS_LINUX) || defined(OS_MACOS)
#include <unistd.h>
#define SLEEP_MS(ms) usleep((ms) * 1000)
#define PATH_SEPARATOR "/"
#endif
Platform-Specific Functions
// Cross-platform file operations
#ifdef OS_WINDOWS
int file_exists(const char *path) {
DWORD attr = GetFileAttributesA(path);
return (attr != INVALID_FILE_ATTRIBUTES);
}
#else
#include <sys/stat.h>
int file_exists(const char *path) {
struct stat st;
return (stat(path, &st) == 0);
}
#endif
// Cross-platform dynamic library loading
#ifdef OS_WINDOWS
#include <windows.h>
typedef HMODULE lib_handle_t;
#define LIB_OPEN(name) LoadLibraryA(name)
#define LIB_CLOSE(h) FreeLibrary(h)
#define LIB_SYM(h, name) GetProcAddress(h, name)
#else
#include <dlfcn.h>
typedef void* lib_handle_t;
#define LIB_OPEN(name) dlopen(name, RTLD_LAZY)
#define LIB_CLOSE(h) dlclose(h)
#define LIB_SYM(h, name) dlsym(h, name)
#endif
Debug and Release Builds
Debug Configuration
// Common debug setup
#ifdef DEBUG
// Enable all assertions
#undef NDEBUG
// Enable verbose logging
#define LOG_LEVEL 4 // Debug level
// Enable memory tracking
#define TRACK_ALLOCATIONS 1
// Disable optimizations markers
#define NO_INLINE __attribute__((noinline))
#else
// Disable assertions
#define NDEBUG 1
// Minimal logging
#define LOG_LEVEL 1 // Errors only
// Disable tracking
#define TRACK_ALLOCATIONS 0
// Allow inlining
#define NO_INLINE
#endif
#include <assert.h>
Debug Macros
#ifdef DEBUG
#define DEBUG_LOG(fmt, ...) \
fprintf(stderr, "[DEBUG %s:%d] " fmt "\n", \
__FILE__, __LINE__, ##__VA_ARGS__)
#define DEBUG_TRACE() \
fprintf(stderr, "[TRACE] %s() in %s:%d\n", \
__func__, __FILE__, __LINE__)
#define DEBUG_ASSERT(cond, msg) do { \
if (!(cond)) { \
fprintf(stderr, "Assert failed: %s\n", msg); \
fprintf(stderr, "Condition: %s\n", #cond); \
fprintf(stderr, "Location: %s:%d\n", __FILE__, __LINE__); \
abort(); \
} \
} while(0)
#define DEBUG_DUMP_HEX(data, len) dump_hex(data, len)
#else
#define DEBUG_LOG(fmt, ...) ((void)0)
#define DEBUG_TRACE() ((void)0)
#define DEBUG_ASSERT(cond, msg) ((void)0)
#define DEBUG_DUMP_HEX(data, len) ((void)0)
#endif
Memory Debugging
#ifdef DEBUG
// Track all allocations
typedef struct {
void *ptr;
size_t size;
const char *file;
int line;
} AllocationInfo;
static AllocationInfo allocations[10000];
static int allocation_count = 0;
#define MALLOC(size) debug_malloc(size, __FILE__, __LINE__)
#define FREE(ptr) debug_free(ptr, __FILE__, __LINE__)
void *debug_malloc(size_t size, const char *file, int line);
void debug_free(void *ptr, const char *file, int line);
void dump_allocations(void);
#else
#define MALLOC(size) malloc(size)
#define FREE(ptr) free(ptr)
#endif
Feature Toggles
Feature Configuration Header
// features.h - Central feature configuration
// Core features
#define FEATURE_LOGGING 1
#define FEATURE_NETWORKING 1
#define FEATURE_DATABASE 0
#define FEATURE_GUI 0
// Optional enhancements
#define FEATURE_COMPRESSION 1
#define FEATURE_ENCRYPTION 0
#define FEATURE_CACHING 1
// Experimental features
#define FEATURE_EXPERIMENTAL_API 0
#define FEATURE_BETA_FEATURES 0
// Build variant
#define BUILD_LITE 0
#define BUILD_STANDARD 1
#define BUILD_PROFESSIONAL 0
// Derived configurations
#if BUILD_LITE
#undef FEATURE_DATABASE
#undef FEATURE_ENCRYPTION
#undef FEATURE_CACHING
#elif BUILD_PROFESSIONAL
#undef FEATURE_COMPRESSION
#define FEATURE_COMPRESSION 1
#undef FEATURE_ENCRYPTION
#define FEATURE_ENCRYPTION 1
#endif
Using Feature Toggles
#include "features.h"
// Conditional includes
#if FEATURE_LOGGING
#include "logging.h"
#endif
#if FEATURE_NETWORKING
#include "network.h"
#endif
#if FEATURE_DATABASE
#include "database.h"
#endif
// Conditional function definitions
void process_data(Data *data) {
#if FEATURE_LOGGING
log_info("Processing data...");
#endif
// Core processing
transform(data);
#if FEATURE_COMPRESSION
compress(data);
#endif
#if FEATURE_ENCRYPTION
encrypt(data);
#endif
#if FEATURE_CACHING
cache_store(data);
#endif
#if FEATURE_LOGGING
log_info("Processing complete");
#endif
}
Feature Toggle Macros
// Helper macros for feature checking
#define IF_FEATURE(feature, code) \
do { if (feature) { code } } while(0)
#define WHEN_ENABLED(feature) if (feature)
// Compile-time feature selection
#if FEATURE_ADVANCED_MATH
#define SIN(x) fast_sin(x)
#define COS(x) fast_cos(x)
#else
#include <math.h>
#define SIN(x) sin(x)
#define COS(x) cos(x)
#endif
Version-Specific Code
Compiler Version Checks
// GCC version check
#ifdef __GNUC__
#define GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__)
#if GCC_VERSION >= 40800
#define HAS_THREAD_LOCAL 1
#endif
#if GCC_VERSION >= 70000
#define HAS_FALLTHROUGH_ATTR 1
#endif
#endif
// Clang version check
#ifdef __clang__
#define CLANG_VERSION (__clang_major__ * 10000 + __clang_minor__ * 100)
#if CLANG_VERSION >= 30400
#define HAS_NULLABILITY 1
#endif
#endif
// MSVC version check
#ifdef _MSC_VER
#if _MSC_VER >= 1900 // VS 2015
#define HAS_CONSTEXPR 1
#endif
#if _MSC_VER >= 1910 // VS 2017
#define HAS_STRUCTURED_BINDINGS 1
#endif
#endif
C Standard Version Checks
// Check C standard version
#if defined(__STDC_VERSION__)
#if __STDC_VERSION__ >= 201112L
#define C11_OR_LATER 1
#endif
#if __STDC_VERSION__ >= 199901L
#define C99_OR_LATER 1
#endif
#endif
// Use C11 features if available
#ifdef C11_OR_LATER
#define STATIC_ASSERT(cond, msg) _Static_assert(cond, msg)
#define NORETURN _Noreturn
#define ALIGNAS(n) _Alignas(n)
#else
#define STATIC_ASSERT(cond, msg) \
typedef char static_assertion_##__LINE__[(cond) ? 1 : -1]
#define NORETURN
#define ALIGNAS(n)
#endif
// C99 flexible array members
#ifdef C99_OR_LATER
#define FLEX_ARRAY
#else
#define FLEX_ARRAY 1
#endif
typedef struct {
int count;
int data[FLEX_ARRAY]; // Flexible array member
} FlexibleArray;
Library Version Checks
// OpenSSL version check
#ifdef OPENSSL_VERSION_NUMBER
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
#define OPENSSL_1_1 1
#else
#define OPENSSL_1_0 1
#endif
#endif
// API differences based on version
#ifdef OPENSSL_1_1
#define SSL_CTX_CREATE() SSL_CTX_new(TLS_method())
#else
#define SSL_CTX_CREATE() SSL_CTX_new(SSLv23_method())
#endif
Best Practices
1. Use Descriptive Macro Names
// Bad
#define F1 1
#define X 100
// Good
#define FEATURE_AUTHENTICATION 1
#define MAX_LOGIN_ATTEMPTS 100
2. Provide Defaults
// Always provide sensible defaults
#ifndef CONFIG_BUFFER_SIZE
#define CONFIG_BUFFER_SIZE 1024
#endif
#ifndef CONFIG_TIMEOUT_MS
#define CONFIG_TIMEOUT_MS 5000
#endif
#ifndef CONFIG_LOG_LEVEL
#define CONFIG_LOG_LEVEL LOG_LEVEL_INFO
#endif
3. Document Conditional Sections
/*
* Platform-specific network initialization
* Windows uses Winsock, Unix uses BSD sockets
*/
#ifdef _WIN32
/* Windows: Initialize Winsock */
WSADATA wsa_data;
WSAStartup(MAKEWORD(2, 2), &wsa_data);
#else
/* Unix: No initialization needed */
#endif
4. Keep Conditionals Simple
// Bad - complex inline condition
#if defined(DEBUG) && !defined(NDEBUG) && (LOG_LEVEL >= 3 || VERBOSE)
// ...
#endif
// Good - break into named macros
#if defined(DEBUG) && !defined(NDEBUG)
#define IS_DEBUG_BUILD 1
#else
#define IS_DEBUG_BUILD 0
#endif
#if LOG_LEVEL >= 3 || defined(VERBOSE)
#define IS_VERBOSE 1
#else
#define IS_VERBOSE 0
#endif
#if IS_DEBUG_BUILD && IS_VERBOSE
// Much clearer!
#endif
5. Use Include Guards
// header.h
#ifndef HEADER_H
#define HEADER_H
// Header content...
#endif /* HEADER_H */
6. Avoid Deep Nesting
// Bad - deeply nested
#ifdef A
#ifdef B
#ifdef C
#ifdef D
// Code
#endif
#endif
#endif
#endif
// Good - combine conditions
#if defined(A) && defined(B) && defined(C) && defined(D)
// Code
#endif
7. Use #error for Invalid Configurations
#if !defined(PLATFORM_WINDOWS) && !defined(PLATFORM_LINUX) && !defined(PLATFORM_MACOS)
#error "No platform defined! Define PLATFORM_WINDOWS, PLATFORM_LINUX, or PLATFORM_MACOS"
#endif
#if defined(FEATURE_A) && defined(FEATURE_B)
#error "FEATURE_A and FEATURE_B are mutually exclusive"
#endif
#if MAX_BUFFER < 256
#error "MAX_BUFFER must be at least 256"
#endif
Common Patterns
Pattern 1: Include Guard
#ifndef MY_HEADER_H
#define MY_HEADER_H
// Header content
#endif /* MY_HEADER_H */
Pattern 2: C++ Compatibility
#ifdef __cplusplus
extern "C" {
#endif
// C declarations
#ifdef __cplusplus
}
#endif
Pattern 3: Optional Feature
#ifdef HAVE_FEATURE_X
#define USE_FEATURE_X 1
#include "feature_x.h"
#else
#define USE_FEATURE_X 0
// Provide stub or alternative
static inline void feature_x_init(void) {}
static inline void feature_x_cleanup(void) {}
#endif
Pattern 4: Debug/Release Toggle
#ifdef NDEBUG
#define RELEASE_BUILD 1
#define DEBUG_BUILD 0
#else
#define RELEASE_BUILD 0
#define DEBUG_BUILD 1
#endif
Pattern 5: Platform Abstraction
// platform.h
#ifdef _WIN32
#include "platform_windows.h"
#elif defined(__linux__)
#include "platform_linux.h"
#elif defined(__APPLE__)
#include "platform_macos.h"
#else
#error "Unsupported platform"
#endif
Pattern 6: Compiler-Specific Attributes
#ifdef __GNUC__
#define UNUSED __attribute__((unused))
#define PACKED __attribute__((packed))
#define LIKELY(x) __builtin_expect(!!(x), 1)
#define UNLIKELY(x) __builtin_expect(!!(x), 0)
#elif defined(_MSC_VER)
#define UNUSED
#define PACKED
#define LIKELY(x) (x)
#define UNLIKELY(x) (x)
#else
#define UNUSED
#define PACKED
#define LIKELY(x) (x)
#define UNLIKELY(x) (x)
#endif
Summary
Key Concepts
- •Conditional compilation selects code at compile-time
- •#ifdef/#ifndef check macro existence
- •#if/#elif/#else evaluate expressions
- •defined() operator enables complex conditions
- •Platform detection enables portable code
- •Feature toggles control optional functionality
Quick Reference
| Directive | Use Case |
|---|---|
#ifdef X | If X is defined (any value) |
#ifndef X | If X is not defined |
#if X | If X evaluates to non-zero |
#if defined(X) | Same as #ifdef, but combinable |
#elif | Else-if (another condition) |
#else | Default case |
#endif | Close conditional block |
#error | Compilation error message |
Best Practices Summary
- •Use descriptive macro names
- •Always provide default values
- •Document conditional sections
- •Keep conditions simple and readable
- •Use
#errorfor invalid configurations - •Avoid deeply nested conditionals
- •Test all configuration combinations
This document is part of the C Programming Language course. For practical exercises, see the accompanying exercises.c file.