Advanced Preprocessor
Introduction
The C preprocessor is a powerful text processing tool that operates before the actual compilation begins. While often overlooked, mastering advanced preprocessor techniques can lead to more maintainable, efficient, and feature-rich C code. Modern C standards have enhanced preprocessor capabilities, making it even more versatile for metaprogramming and code generation.
This chapter explores advanced preprocessor features including macro definitions, conditional compilation, pragma directives, and token manipulation techniques that enable sophisticated code generation and platform-specific optimizations.
Macro Definitions and Function-like Macros
Macros are the most fundamental preprocessor feature, allowing for text substitution and code generation.
Advanced Macro Techniques
#include <stdio.h>
#include <stdlib.h>
// Stringification operator (#)
#define STRINGIFY(x) #x
#define PRINT_VAR(var) printf(#var " = %d\n", var)
// Token pasting operator (##)
#define CONCAT(a, b) a##b
#define DECLARE_VAR(type, name) type CONCAT(my_, name)
// Variadic macros (C99)
#define DEBUG_PRINT(fmt, ...) \
fprintf(stderr, "[DEBUG] %s:%d: " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG(level, fmt, ...) \
printf("[" #level "] " fmt "\n", ##__VA_ARGS__)
// Macro overloading based on number of arguments
#define GET_MACRO(_1, _2, _3, NAME, ...) NAME
#define ADD2(a, b) ((a) + (b))
#define ADD3(a, b, c) ((a) + (b) + (c))
#define ADD(...) GET_MACRO(__VA_ARGS__, ADD3, ADD2)(__VA_ARGS__)
int main() {
int x = 42;
// Stringification
printf("Value of x: %s\n", STRINGIFY(x));
PRINT_VAR(x);
// Token pasting
DECLARE_VAR(int, counter) = 100;
printf("my_counter = %d\n", my_counter);
// Variadic macros
DEBUG_PRINT("This is a debug message");
DEBUG_PRINT("Value of x is %d", x);
LOG(INFO, "Application started");
LOG(ERROR, "Failed to open file: %s", "config.txt");
// Macro overloading
printf("ADD(1, 2) = %d\n", ADD(1, 2));
printf("ADD(1, 2, 3) = %d\n", ADD(1, 2, 3));
return 0;
}Generic Programming with Macros
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Generic swap macro
#define SWAP(a, b) do { \
typeof(a) temp = (a); \
(a) = (b); \
(b) = temp; \
} while(0)
// Generic maximum macro
#define MAX(a, b) ({ \
typeof(a) _a = (a); \
typeof(b) _b = (b); \
_a > _b ? _a : _b; \
})
// Generic container_of macro (similar to Linux kernel)
#define container_of(ptr, type, member) ({ \
const typeof(((type *)0)->member) * __mptr = (ptr); \
(type *)((char *)__mptr - offsetof(type, member)); \
})
#define offsetof(type, member) ((size_t) &((type *)0)->member)
typedef struct {
int id;
char name[50];
double value;
} Item;
int main() {
// Generic swap
int x = 10, y = 20;
printf("Before swap: x=%d, y=%d\n", x, y);
SWAP(x, y);
printf("After swap: x=%d, y=%d\n", x, y);
double a = 3.14, b = 2.71;
printf("Before swap: a=%.2f, b=%.2f\n", a, b);
SWAP(a, b);
printf("After swap: a=%.2f, b=%.2f\n", a, b);
// Generic maximum
printf("MAX(10, 20) = %d\n", MAX(10, 20));
printf("MAX(3.14, 2.71) = %.2f\n", MAX(3.14, 2.71));
// Container of example
Item item = {1, "Test Item", 99.99};
char *name_ptr = item.name;
// Get the container from a member pointer
Item *container = container_of(name_ptr, Item, name);
printf("Container ID: %d, Name: %s, Value: %.2f\n",
container->id, container->name, container->value);
return 0;
}Conditional Compilation
Conditional compilation allows you to include or exclude code based on preprocessor conditions, enabling platform-specific code and feature toggles.
Platform-Specific Compilation
#include <stdio.h>
// Platform detection
#if defined(_WIN32) || defined(_WIN64)
#define PLATFORM_WINDOWS
#include <windows.h>
#elif defined(__linux__)
#define PLATFORM_LINUX
#include <unistd.h>
#elif defined(__APPLE__)
#define PLATFORM_MACOS
#include <unistd.h>
#endif
// Compiler detection
#if defined(__GNUC__)
#define COMPILER_GCC
#define NORETURN __attribute__((noreturn))
#define UNUSED __attribute__((unused))
#elif defined(_MSC_VER)
#define COMPILER_MSVC
#define NORETURN __declspec(noreturn)
#define UNUSED
#endif
// Feature detection
#if __STDC_VERSION__ >= 202311L
#define C23_AVAILABLE
#define HAS_TYPEOF typeof
#elif __STDC_VERSION__ >= 201112L
#define C11_AVAILABLE
#define HAS_TYPEOF __typeof__
#else
#define C99_AVAILABLE
#endif
// Debug vs Release builds
#ifdef DEBUG
#define DBG_PRINT(fmt, ...) fprintf(stderr, "[DEBUG] " fmt "\n", ##__VA_ARGS__)
#define ASSERT(condition) do { \
if (!(condition)) { \
fprintf(stderr, "Assertion failed: %s at %s:%d\n", \
#condition, __FILE__, __LINE__); \
abort(); \
} \
} while(0)
#else
#define DBG_PRINT(fmt, ...) do {} while(0)
#define ASSERT(condition) do {} while(0)
#endif
// Feature toggles
#define FEATURE_NETWORKING 1
#define FEATURE_DATABASE 0
#if FEATURE_NETWORKING
#include <sys/socket.h>
void init_networking(void) {
printf("Networking initialized\n");
}
#else
void init_networking(void) {
printf("Networking disabled\n");
}
#endif
#if FEATURE_DATABASE
#include <sqlite3.h>
void init_database(void) {
printf("Database initialized\n");
}
#else
void init_database(void) {
printf("Database disabled\n");
}
#endif
int main() {
printf("Platform: ");
#if defined(PLATFORM_WINDOWS)
printf("Windows\n");
#elif defined(PLATFORM_LINUX)
printf("Linux\n");
#elif defined(PLATFORM_MACOS)
printf("macOS\n");
#else
printf("Unknown\n");
#endif
printf("Compiler: ");
#ifdef COMPILER_GCC
printf("GCC\n");
#elif defined(COMPILER_MSVC)
printf("MSVC\n");
#else
printf("Unknown\n");
#endif
printf("C Standard: ");
#ifdef C23_AVAILABLE
printf("C23\n");
#elif defined(C11_AVAILABLE)
printf("C11\n");
#else
printf("C99 or earlier\n");
#endif
// Debug printing
DBG_PRINT("Debug message");
DBG_PRINT("Value of x: %d", 42);
// Assertions
int x = 10;
ASSERT(x > 0);
// Feature initialization
init_networking();
init_database();
return 0;
}Version-Specific Code
#include <stdio.h>
// Version checking macros
#define VERSION_CHECK(major, minor, patch) \
((major) * 10000 + (minor) * 100 + (patch))
#define LIBRARY_VERSION VERSION_CHECK(2, 3, 1)
// API version compatibility
#if LIBRARY_VERSION >= VERSION_CHECK(2, 0, 0)
#define NEW_API_AVAILABLE
void new_api_function(void) {
printf("Using new API\n");
}
#endif
#if LIBRARY_VERSION >= VERSION_CHECK(2, 3, 0)
#define ENHANCED_API_AVAILABLE
void enhanced_api_function(void) {
printf("Using enhanced API\n");
}
#endif
// Backward compatibility
#if LIBRARY_VERSION < VERSION_CHECK(2, 0, 0)
void new_api_function(void) {
printf("Using legacy implementation\n");
}
#endif
int main() {
printf("Library version: %d.%d.%d\n",
LIBRARY_VERSION / 10000,
(LIBRARY_VERSION % 10000) / 100,
LIBRARY_VERSION % 100);
#ifdef NEW_API_AVAILABLE
new_api_function();
#endif
#ifdef ENHANCED_API_AVAILABLE
enhanced_api_function();
#endif
return 0;
}Pragma Directives
Pragma directives provide compiler-specific instructions and hints that can optimize code generation and control compilation behavior.
Common Pragma Directives
#include <stdio.h>
#include <stdlib.h>
// Region folding (supported by many IDEs)
#pragma region Utility Functions
static inline int max(int a, int b) {
return a > b ? a : b;
}
static inline int min(int a, int b) {
return a < b ? a : b;
}
#pragma endregion
// Optimization hints
#pragma GCC push_options
#pragma GCC optimize("O3")
// Performance-critical function
double compute_pi(int iterations) {
double pi = 0.0;
double denominator = 1.0;
for (int i = 0; i < iterations; i++) {
if (i % 2 == 0) {
pi += 4.0 / denominator;
} else {
pi -= 4.0 / denominator;
}
denominator += 2.0;
}
return pi;
}
#pragma GCC pop_options
// Warning control
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-variable"
void example_with_warnings(void) {
int unused_var = 42; // This warning is suppressed
printf("Function with suppressed warnings\n");
}
#pragma GCC diagnostic pop
// Structure packing
#pragma pack(push, 1)
typedef struct {
char a; // 1 byte
int b; // 4 bytes (normally padded to offset 4)
short c; // 2 bytes (normally padded to offset 8)
} PackedStruct; // Total size: 7 bytes instead of 12
#pragma pack(pop)
// Once-only inclusion (alternative to include guards)
#pragma once
// Message directives (compiler-specific)
#pragma message("Compiling with advanced preprocessor features")
int main() {
printf("Max of 10 and 20: %d\n", max(10, 20));
printf("Min of 10 and 20: %d\n", min(10, 20));
double pi = compute_pi(1000000);
printf("Computed PI: %.10f\n", pi);
example_with_warnings();
printf("Size of PackedStruct: %zu bytes\n", sizeof(PackedStruct));
return 0;
}Compiler-Specific Pragmas
#include <stdio.h>
// GCC-specific pragmas
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
// Function inlining hints
#pragma GCC push_options
#pragma GCC optimize("inline-functions-called-once")
static inline void optimized_function(void) {
printf("This function may be inlined\n");
}
#pragma GCC pop_options
#pragma GCC diagnostic pop
#endif
// MSVC-specific pragmas
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable: 4996) // Disable deprecated function warnings
// Function inlining
#pragma inline_depth(255)
#pragma inline_recursion(on)
static __inline void msvc_optimized_function(void) {
printf("MSVC optimized function\n");
}
#pragma warning(pop)
#endif
// Clang-specific pragmas
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
// Loop optimization hints
#pragma clang loop vectorize(enable)
void clang_optimized_loop(int *array, int size) {
for (int i = 0; i < size; i++) {
array[i] *= 2;
}
}
#pragma clang diagnostic pop
#endif
int main() {
#ifdef __GNUC__
optimized_function();
#endif
#ifdef _MSC_VER
msvc_optimized_function();
#endif
#ifdef __clang__
int array[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
clang_optimized_loop(array, 10);
printf("Array[0] after optimization: %d\n", array[0]);
#endif
return 0;
}Predefined Macros
C provides numerous predefined macros that offer information about the compilation environment.
System and Compiler Information
#include <stdio.h>
#include <time.h>
void print_compilation_info(void) {
printf("=== Compilation Information ===\n");
// Standard version
printf("Standard version: %ld\n", __STDC_VERSION__);
#ifdef __STDC_VERSION__
#if __STDC_VERSION__ >= 202311L
printf("C Standard: C23\n");
#elif __STDC_VERSION__ >= 201112L
printf("C Standard: C11\n");
#elif __STDC_VERSION__ >= 199901L
printf("C Standard: C99\n");
#else
printf("C Standard: C89/C90\n");
#endif
#endif
// Compiler information
#ifdef __GNUC__
printf("Compiler: GCC %d.%d.%d\n", __GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__);
#endif
#ifdef __clang__
printf("Compiler: Clang %d.%d.%d\n", __clang_major__, __clang_minor__, __clang_patchlevel__);
#endif
#ifdef _MSC_VER
printf("Compiler: MSVC %d\n", _MSC_VER);
#endif
// Platform information
#ifdef __x86_64__
printf("Architecture: x86_64\n");
#elif defined(__i386__)
printf("Architecture: x86\n");
#elif defined(__aarch64__)
printf("Architecture: ARM64\n");
#elif defined(__arm__)
printf("Architecture: ARM\n");
#endif
#ifdef __LP64__
printf("Data model: LP64\n");
#endif
// Date and time
printf("Compilation date: %s\n", __DATE__);
printf("Compilation time: %s\n", __TIME__);
// File and line information
printf("Current file: %s\n", __FILE__);
printf("Current line: %d\n", __LINE__);
printf("Function name: %s\n", __func__);
printf("\n");
}
// Using predefined macros for debugging
#ifdef DEBUG
#define TRACE() printf("TRACE: %s() in %s:%d\n", __func__, __FILE__, __LINE__)
#else
#define TRACE() do {} while(0)
#endif
void function_a(void) {
TRACE();
printf("Inside function_a\n");
}
void function_b(void) {
TRACE();
printf("Inside function_b\n");
function_a();
}
int main() {
print_compilation_info();
function_b();
// Counter macro (implementation-defined)
printf("This is compilation #%d\n", __COUNTER__);
printf("This is compilation #%d\n", __COUNTER__);
return 0;
}Token Manipulation and Advanced Techniques
Advanced preprocessor techniques involve complex token manipulation and metaprogramming.
Deferred Macro Expansion
#include <stdio.h>
// Deferred expansion technique
#define EMPTY()
#define DEFER(id) id EMPTY()
#define OBSTRUCT(...) __VA_ARGS__ DEFER(EMPTY)()
#define EXPAND(...) __VA_ARGS__
#define EVAL(...) EVAL1(EVAL1(EVAL1(__VA_ARGS__)))
#define EVAL1(...) EVAL2(EVAL2(EVAL2(__VA_ARGS__)))
#define EVAL2(...) EVAL3(EVAL3(EVAL3(__VA_ARGS__)))
#define EVAL3(...) EVAL4(EVAL4(EVAL4(__VA_ARGS__)))
#define EVAL4(...) __VA_ARGS__
// Recursive macro example
#define WHEN(c) IF_##c
#define IF_1(t, f) t
#define IF_0(t, f) f
#define COMMA() ,
#define SEMICOLON() ;
#define REPEAT_0(f, ...)
#define REPEAT_1(f, x, ...) f(x) REPEAT_0(f, __VA_ARGS__)
#define REPEAT_2(f, x, ...) f(x) REPEAT_1(f, __VA_ARGS__)
#define REPEAT_3(f, x, ...) f(x) REPEAT_2(f, __VA_ARGS__)
#define REPEAT_4(f, x, ...) f(x) REPEAT_3(f, __VA_ARGS__)
#define REPEAT_N(n, f, ...) EVAL(REPEAT_##n(f, __VA_ARGS__))
#define PRINT_ARG(x) printf("%s = %d\n", #x, x);
int main() {
int a = 1, b = 2, c = 3, d = 4;
printf("Repeating PRINT_ARG macro:\n");
REPEAT_N(4, PRINT_ARG, a, b, c, d)
return 0;
}X-Macros Pattern
#include <stdio.h>
#include <string.h>
// X-Macros for enumerations and string tables
#define COLOR_TABLE \
X(RED, "red", 0xFF0000) \
X(GREEN, "green", 0x00FF00) \
X(BLUE, "blue", 0x0000FF) \
X(YELLOW, "yellow", 0xFFFF00) \
X(PURPLE, "purple", 0xFF00FF) \
X(CYAN, "cyan", 0x00FFFF) \
X(WHITE, "white", 0xFFFFFF) \
X(BLACK, "black", 0x000000)
// Generate enumeration
#define X(name, string, value) COLOR_##name,
typedef enum {
COLOR_TABLE
COLOR_COUNT
} Color;
#undef X
// Generate string array
#define X(name, string, value) string,
static const char *color_strings[] = {
COLOR_TABLE
};
#undef X
// Generate value array
#define X(name, string, value) value,
static const int color_values[] = {
COLOR_TABLE
};
#undef X
// Generate function to get string
const char *color_to_string(Color color) {
if (color >= 0 && color < COLOR_COUNT) {
return color_strings[color];
}
return "unknown";
}
// Generate function to get value
int color_to_value(Color color) {
if (color >= 0 && color < COLOR_COUNT) {
return color_values[color];
}
return 0;
}
// Generate lookup function
Color string_to_color(const char *str) {
for (int i = 0; i < COLOR_COUNT; i++) {
if (strcmp(str, color_strings[i]) == 0) {
return (Color)i;
}
}
return COLOR_COUNT; // Invalid color
}
int main() {
printf("Color enumeration:\n");
for (int i = 0; i < COLOR_COUNT; i++) {
printf(" %d: %s (0x%06X)\n", i, color_to_string((Color)i), color_to_value((Color)i));
}
printf("\nString to color lookup:\n");
const char *test_colors[] = {"red", "blue", "purple", "invalid"};
for (int i = 0; i < 4; i++) {
Color c = string_to_color(test_colors[i]);
if (c < COLOR_COUNT) {
printf(" %s -> %s (0x%06X)\n", test_colors[i], color_to_string(c), color_to_value(c));
} else {
printf(" %s -> invalid color\n", test_colors[i]);
}
}
return 0;
}C23 Enhanced Preprocessor Features
C23 introduces several new preprocessor features that enhance metaprogramming capabilities.
#elifdef and #elifndef
#include <stdio.h>
// Define some macros for testing
#define DEBUG_MODE
// #define RELEASE_MODE
// #define TESTING_MODE
int main() {
printf("Preprocessor conditional compilation:\n");
#if defined(DEBUG_MODE)
printf(" Debug mode is enabled\n");
#elifdef RELEASE_MODE
printf(" Release mode is enabled\n");
#elifndef TESTING_MODE
printf(" Testing mode is not defined\n");
#else
printf(" Unknown mode\n");
#endif
return 0;
}#warning Directive
#include <stdio.h>
// Compiler version check
#if __STDC_VERSION__ < 202311L
#warning "This code is designed for C23 or later"
#endif
// Platform-specific warnings
#ifdef __APPLE__
#warning "This code may need adjustments for macOS"
#endif
// Feature availability warnings
#ifndef __STDC_LIB_EXT1__
#warning "Bounds-checking functions not available"
#endif
int main() {
printf("Program running with warnings (if any)\n");
#ifdef __GNUC__
#warning "Consider using Clang for better C23 support"
#endif
return 0;
}Digit Separators in Preprocessor
#include <stdio.h>
// Using digit separators in preprocessor expressions
#define ONE_MILLION 1'000'000
#define MAX_BUFFER_SIZE 10'000
#define BINARY_PATTERN 0b1010'0101'1100'1111
int main() {
printf("One million: %d\n", ONE_MILLION);
printf("Max buffer size: %d\n", MAX_BUFFER_SIZE);
printf("Binary pattern: 0x%X\n", BINARY_PATTERN);
// Using in preprocessor conditionals
#if ONE_MILLION > 500'000
printf("One million is greater than 500 thousand\n");
#endif
return 0;
}Practical Examples
Configuration System Using Preprocessor
#include <stdio.h>
// Configuration system using preprocessor
// Define default values
#ifndef CONFIG_MAX_CONNECTIONS
#define CONFIG_MAX_CONNECTIONS 100
#endif
#ifndef CONFIG_TIMEOUT
#define CONFIG_TIMEOUT 30
#endif
#ifndef CONFIG_LOG_LEVEL
#define CONFIG_LOG_LEVEL 2
#endif
// Feature flags
#define FEATURE_NETWORKING 1
#define FEATURE_DATABASE 1
#define FEATURE_SECURITY 0
// Platform-specific configurations
#ifdef _WIN32
#define PATH_SEPARATOR '\\'
#define DEFAULT_CONFIG_FILE "config.ini"
#else
#define PATH_SEPARATOR '/'
#define DEFAULT_CONFIG_FILE "/etc/myapp/config.ini"
#endif
// Debug configuration
#ifdef DEBUG
#define LOG_LEVEL CONFIG_LOG_LEVEL
#define ENABLE_ASSERTIONS 1
#define VERBOSE_LOGGING 1
#else
#define LOG_LEVEL (CONFIG_LOG_LEVEL - 1)
#define ENABLE_ASSERTIONS 0
#define VERBOSE_LOGGING 0
#endif
// Conditional compilation based on features
#if FEATURE_NETWORKING
#include <sys/socket.h>
#define NETWORK_BUFFER_SIZE 8192
void init_networking(void) {
printf("Networking initialized with buffer size: %d\n", NETWORK_BUFFER_SIZE);
}
#else
void init_networking(void) {
printf("Networking disabled\n");
}
#endif
#if FEATURE_DATABASE
#include <sqlite3.h>
#define DB_CONNECTION_POOL_SIZE 10
void init_database(void) {
printf("Database initialized with connection pool size: %d\n", DB_CONNECTION_POOL_SIZE);
}
#else
void init_database(void) {
printf("Database disabled\n");
}
#endif
#if FEATURE_SECURITY
#include <openssl/ssl.h>
#define SSL_ENABLED 1
void init_security(void) {
printf("Security initialized with SSL enabled\n");
}
#else
#define SSL_ENABLED 0
void init_security(void) {
printf("Security disabled\n");
}
#endif
void print_configuration(void) {
printf("=== Application Configuration ===\n");
printf("Max connections: %d\n", CONFIG_MAX_CONNECTIONS);
printf("Timeout: %d seconds\n", CONFIG_TIMEOUT);
printf("Log level: %d\n", LOG_LEVEL);
printf("Path separator: '%c'\n", PATH_SEPARATOR);
printf("Default config file: %s\n", DEFAULT_CONFIG_FILE);
printf("Assertions: %s\n", ENABLE_ASSERTIONS ? "enabled" : "disabled");
printf("Verbose logging: %s\n", VERBOSE_LOGGING ? "enabled" : "disabled");
printf("\n");
printf("=== Feature Status ===\n");
printf("Networking: %s\n", FEATURE_NETWORKING ? "enabled" : "disabled");
printf("Database: %s\n", FEATURE_DATABASE ? "enabled" : "disabled");
printf("Security: %s\n", FEATURE_SECURITY ? "enabled" : "disabled");
printf("SSL: %s\n", SSL_ENABLED ? "enabled" : "disabled");
printf("\n");
}
int main() {
print_configuration();
init_networking();
init_database();
init_security();
return 0;
}Advanced Error Handling with Preprocessor
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
// Advanced error handling macros
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
// Error checking with automatic cleanup
#define CHECK_ERRNO(call) do { \
errno = 0; \
call; \
if (errno != 0) { \
fprintf(stderr, "Error in %s at %s:%d: %s\n", \
#call, __FILE__, __LINE__, strerror(errno)); \
exit(EXIT_FAILURE); \
} \
} while(0)
// Resource management with automatic cleanup
#define CLEANUP_VAR(type, name, cleanup_func) \
type name = NULL; \
defer_cleanup(cleanup_func, &name)
// Defer mechanism (simplified)
typedef struct defer_node {
void (*func)(void*);
void *arg;
struct defer_node *next;
} defer_node_t;
static defer_node_t *defer_stack = NULL;
void defer_cleanup(void (*func)(void*), void *arg) {
defer_node_t *node = malloc(sizeof(defer_node_t));
node->func = func;
node->arg = arg;
node->next = defer_stack;
defer_stack = node;
}
void execute_deferred(void) {
while (defer_stack) {
defer_node_t *node = defer_stack;
defer_stack = node->next;
node->func(node->arg);
free(node);
}
}
#define defer(func, arg) defer_cleanup(func, arg)
// Cleanup functions
void free_ptr(void *ptr) {
free(*(void**)ptr);
}
void fclose_ptr(void *ptr) {
if (*(FILE**)ptr) {
fclose(*(FILE**)ptr);
}
}
int main() {
printf("Advanced error handling with preprocessor\n");
// File operations with automatic error checking
CLEANUP_VAR(FILE*, fp, fclose_ptr);
fp = fopen("test.txt", "w");
if (!fp) {
perror("fopen");
exit(EXIT_FAILURE);
}
CHECK_ERRNO(fprintf(fp, "Hello, World!\n"));
CHECK_ERRNO(fflush(fp));
// Memory allocation with automatic cleanup
CLEANUP_VAR(char*, buffer, free_ptr);
buffer = malloc(100);
if (!buffer) {
fprintf(stderr, "Memory allocation failed\n");
exit(EXIT_FAILURE);
}
strcpy(buffer, "This is a test buffer");
printf("Buffer content: %s\n", buffer);
// Automatic cleanup when scope ends
execute_deferred();
return 0;
}Summary
Advanced preprocessor techniques provide powerful metaprogramming capabilities in C:
- Macro Definitions: Stringification, token pasting, variadic macros, and generic programming
- Conditional Compilation: Platform-specific code, feature toggles, and version compatibility
- Pragma Directives: Compiler hints, optimization control, and warning management
- Predefined Macros: System information, compilation context, and debugging support
- Token Manipulation: Deferred expansion, X-Macros, and complex metaprogramming
- C23 Enhancements: Improved conditional directives and digit separators
Mastering these advanced preprocessor techniques enables C developers to write more maintainable, efficient, and feature-rich code while leveraging the full power of the C preprocessor for metaprogramming and code generation.