READMEC Programming

README

Preprocessor / Conditional Compilation

Concept Lesson
Advanced
4 min

Learning Objective

Understand Preprocessor well enough to explain it, recognize it in C Programming, and apply it in a small task.

Why It Matters

This concept is part of the foundation that later lessons and projects assume you already understand.

Table Of ContentsWhy Use Conditional CompilationPreprocessor Vs Runtime ConditionalsBasic Conditional DirectivesComplete List Of Directives
Private notes
0/8000

Notes stay private to your browser until account sync is configured.

README
3 min read18 headings

Conditional Compilation in C

Table of Contents

  1. Introduction
  2. Basic Conditional Directives
  3. #ifdef and #ifndef
  4. #if, #elif, #else, #endif
  5. defined Operator
  6. Conditional Compilation Use Cases
  7. Platform-Specific Code
  8. Debug and Release Builds
  9. Feature Toggles
  10. Version-Specific Code
  11. Best Practices
  12. 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?

  1. Platform Portability: Write code that compiles on different operating systems
  2. Debug vs Release: Include debugging code only in development builds
  3. Feature Flags: Enable/disable features without changing code
  4. Hardware Adaptation: Optimize for different hardware architectures
  5. API Versions: Support multiple versions of libraries
  6. Code Organization: Manage large codebases with optional components

Preprocessor vs Runtime Conditionals

AspectPreprocessorRuntime
EvaluationCompile-timeRuntime
Binary SizeExcluded code absentAll code present
PerformanceZero overheadCondition check overhead
FlexibilityFixed at compileCan change during execution
DebuggingCan't debug excluded codeAll code debuggable

Basic Conditional Directives

The C preprocessor provides several directives for conditional compilation:

Complete List of Directives

DirectivePurpose
#ifIf expression is true (non-zero)
#ifdefIf macro is defined
#ifndefIf macro is not defined
#elifElse if (another condition)
#elseElse (no condition)
#endifEnd of conditional block
#defineDefine a macro
#undefUndefine 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

  1. Conditional compilation selects code at compile-time
  2. #ifdef/#ifndef check macro existence
  3. #if/#elif/#else evaluate expressions
  4. defined() operator enables complex conditions
  5. Platform detection enables portable code
  6. Feature toggles control optional functionality

Quick Reference

DirectiveUse Case
#ifdef XIf X is defined (any value)
#ifndef XIf X is not defined
#if XIf X evaluates to non-zero
#if defined(X)Same as #ifdef, but combinable
#elifElse-if (another condition)
#elseDefault case
#endifClose conditional block
#errorCompilation error message

Best Practices Summary

  1. Use descriptive macro names
  2. Always provide default values
  3. Document conditional sections
  4. Keep conditions simple and readable
  5. Use #error for invalid configurations
  6. Avoid deeply nested conditionals
  7. Test all configuration combinations

This document is part of the C Programming Language course. For practical exercises, see the accompanying exercises.c file.

Skill Check

Test this lesson

Answer 4 quick questions to lock in the lesson and feed your adaptive practice queue.

--
Score
0/4
Answered
Not attempted
Status
1

Which module does this lesson belong to?

2

Which section is covered in this lesson content?

3

Which term is most central to this lesson?

4

What is the best way to use this lesson for real learning?

Your answers save locally first, then sync when account storage is available.
Practice queue