C Language Preprocessor (Part 2): Conditional Compilation and Practical Directives

1. Conditional Compilation Directives

Conditional compilation allows selective compilation of code based on different conditions, which is a key technology for achieving cross-platform compatibility, debugging switches, and version control.

1.1 <span>#ifdef</span> / <span>#ifndef</span>

Check if a macro is defined:

// Debug switch
#ifdef DEBUG
    printf("Debug: x = %d\n", x);
#endif

// Prevent multiple inclusions
#ifndef CONFIG_H
#define CONFIG_H
    // Header file content
#endif

Practical application example: Handling platform differences

#ifdef _WIN32
    #include <windows.h>
    #define SLEEP(ms) Sleep(ms)
#else
    #include <unistd.h>
    #define SLEEP(ms) usleep((ms) * 1000)
#endif

// Cross-platform usage
SLEEP(100);  // Works on both Windows and Linux

1.2 <span>#if</span> / <span>#elif</span> / <span>#else</span>

Supports more complex conditional checks:

#if defined(__linux__)
    #define PLATFORM "Linux"
#elif defined(_WIN32)
    #define PLATFORM "Windows"
#elif defined(__APPLE__)
    #define PLATFORM "macOS"
#else
    #define PLATFORM "Unknown"
#endif

printf("Running on: %s\n", PLATFORM);

Expression evaluation:

#define VERSION 3

#if VERSION >= 3
    void new_feature() {
        printf("New feature available\n");
    }
#else
    void old_feature() {
        printf("Using legacy version\n");
    }
#endif

1.3 <span>defined</span> operator

Used to check if a macro is defined, often used with <span>#if</span>:

// Method 1: Using #ifdef
#ifdef DEBUG

// Method 2: Using defined (recommended, can combine complex conditions)
#if defined(DEBUG)

// Combined conditions
#if defined(DEBUG) && !defined(RELEASE)
    printf("Debug mode (not release)\n");
#endif

#if defined(_WIN32) || defined(__linux__)
    // Code for Windows or Linux platforms
#endif

1.4 Practical case: Feature switches

// config.h
#define FEATURE_LOGGING    1
#define FEATURE_ENCRYPTION 1
#define FEATURE_CACHE      0

// main.c
#if FEATURE_LOGGING
    #define LOG(msg) printf("[LOG] %s\n", msg)
#else
    #define LOG(msg)  // No operation
#endif

#if FEATURE_ENCRYPTION
    void encrypt_data(void* data, size_t len) {
        // Encryption implementation
    }
#else
    void encrypt_data(void* data, size_t len) {
        // No implementation or throw error
    }
#endif

// Code usage remains unchanged, automatically selected at compile time
LOG("Application started");
encrypt_data(buffer, size);

2. Other Practical Directives

2.1 <span>#error</span> – Compilation error

Generates a compilation error actively, used for configuration checks:

#ifndef CONFIG_LOADED
    #error "Configuration file not included!"
#endif

#if MAX_BUFFER_SIZE < 1024
    #error "MAX_BUFFER_SIZE must be at least 1024"
#endif

// Check C standard version
#if __STDC_VERSION__ < 199901L
    #error "This code requires C99 or later"
#endif

Application scenarios:

  • Force configuration checks
  • Unsupported platforms/compilers
  • Dependency checks

2.2 <span>#warning</span> – Compilation warning

Generates a compilation warning (non-standard but supported by GCC/Clang):

#ifndef OPTIMIZED
    #warning "Compiling without optimization, performance may be poor"
#endif

#if DEBUG
    #warning "Debug mode enabled, not for production!"
#endif

2.3 <span>#line</span> – Line number control

Modifies the filename and line number reported by the compiler:

#line 100 "custom_file.c"
// Subsequent code errors will report from custom_file.c:100

// Actual application: Code generator
// generated_code.c (generated by tool)
#line 1 "original_template.tpl"
void generated_function() {
    // Errors will point to the original template file
}

Uses:

  • Code generation tools
  • Debugging generated code
  • Retaining original source file information

2.4 <span>#pragma</span> – Compiler directives

<span>#pragma</span> is used to pass special directives to the compiler, specific functionality depends on the compiler:

Common pragma directives:

// 1. Prevent multiple inclusions of header files
#pragma once

// 2. Structure memory alignment
#pragma pack(push, 1)  // 1-byte alignment
struct PackedData {
    char a;
    int b;
};
#pragma pack(pop)      // Restore default alignment

// 3. Disable specific warnings (GCC/Clang)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-variable"
    int unused_var;  // Will not generate a warning
#pragma GCC diagnostic pop

// 4. Disable specific warnings (MSVC)
#pragma warning(disable: 4996)  // Disable warnings for unsafe functions

// 5. Code segment control
#pragma code_seg(".my_section")
void special_function() {
    // Placed in a specific code segment
}

// 6. Optimization control
#pragma GCC optimize("O3")
void performance_critical_function() {
    // High optimization level
}

Practical application: Embedded development

// Place data in Flash instead of RAM
#pragma DATA_SECTION(lookup_table, ".const")
const int lookup_table[256] = { /* ... */ };

// Interrupt service routine
#pragma CODE_STATE(ISR_Timer, 32)
#pragma INTERRUPT(ISR_Timer)
void ISR_Timer(void) {
    // Interrupt handling
}

3. Predefined Macros

Standard macros built into the compiler, used to obtain compilation information:

3.1 Standard Predefined Macros

#include <stdio.h>

int main() {
    printf("File: %s\n", __FILE__);           // Current file name
    printf("Line: %d\n", __LINE__);           // Current line number
    printf("Date: %s\n", __DATE__);           // Compilation date (Mmm dd yyyy)
    printf("Time: %s\n", __TIME__);           // Compilation time (hh:mm:ss)
    printf("Compiled: %s %s\n", __DATE__, __TIME__);
    
    #ifdef __STDC__
        printf("Standard C: %ld\n", __STDC_VERSION__);
    #endif
    
    return 0;
}

// Output example:
// File: main.c
// Line: 5
// Date: Nov 10 2025
// Time: 14:35:22

3.2 Function Name Macros

void my_function() {
    printf("Function: %s\n", __func__);       // C99: Function name
    printf("Function: %s\n", __FUNCTION__);   // GCC extension
}

// Output: Function: my_function

3.3 Utility Macros: Assertions and Logging

// Custom assertion
#define ASSERT(expr) \
    if (!(expr)) { \
        fprintf(stderr, "Assertion failed: %s, file %s, line %d\n", \
                #expr, __FILE__, __LINE__); \
        abort(); \
    }

// Logging with location information
#define LOG_ERROR(fmt, ...) \
    fprintf(stderr, "[ERROR] %s:%d:%s() " fmt "\n", \
            __FILE__, __LINE__, __func__, ##__VA_ARGS__)

// Usage
ASSERT(ptr != NULL);
LOG_ERROR("Failed to open file: %s", filename);

// Output:
// [ERROR] main.c:45:process_file() Failed to open file: data.txt

3.4 Compiler-Specific Macros

// Detect compiler
#ifdef __GNUC__
    printf("GCC version: %d.%d.%d\n", 
           __GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__);
#endif

#ifdef _MSC_VER
    printf("MSVC version: %d\n", _MSC_VER);
#endif

#ifdef __clang__
    printf("Clang version: %s\n", __clang_version__);
#endif

// Detect operating system
#ifdef __linux__
    printf("Linux system\n");
#elif defined(_WIN32)
    printf("Windows system\n");
#elif defined(__APPLE__)
    printf("macOS system\n");
#endif

4. Comprehensive Practice: Building Information Module

// build_info.h
#pragma once

#define BUILD_DATE __DATE__
#define BUILD_TIME __TIME__
#define BUILD_FILE __FILE__

#ifdef DEBUG
    #define BUILD_TYPE "Debug"
#else
    #define BUILD_TYPE "Release"
#endif

#define PRINT_BUILD_INFO() \
    printf("=== Build Information ===\n"); \
    printf("Version: %d.%d.%d\n", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH); \
    printf("Build Type: %s\n", BUILD_TYPE); \
    printf("Compiled: %s %s\n", BUILD_DATE, BUILD_TIME); \
    printf("Compiler: "); \
    PRINT_COMPILER_INFO(); \
    printf("========================\n")

#define PRINT_COMPILER_INFO() \
    do { \
        _Pragma("GCC diagnostic push") \
        _Pragma("GCC diagnostic ignored \"-Wdate-time\"") \
        printf("GCC %d.%d.%d\n", __GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__); \
        _Pragma("GCC diagnostic pop") \
    } while(0)

Summary

Conditional compilation and preprocessor directives are powerful tools in C language:

Conditional Compilation: Achieve cross-platform compatibility, feature switches, version control#error/#warning: Compile-time checks to detect issues early#pragma: Fine control over compilation behaviorPredefined Macros: Obtain compilation information, assist debugging

The next article will introduceLinux kernel macro techniques and advanced macro programming techniques.

Leave a Comment