Preprocessor Basics
Master the C preprocessor: #include, #define, macros, conditional compilation, and text substitution. Learn how preprocessing happens before compilation, enabling code generation, constants, and platform-specific builds.
What is the Preprocessor?
The preprocessor processes source code before compilation. It handles directives starting with #: including files, defining constants, expanding macros, conditional compilation. Output is pure C code fed to the compiler. Understanding preprocessing helps debug macro errors and use advanced techniques.
/* Preprocessing happens first */
/* Source file (input.c): */
#include <stdio.h>
#define MAX 100
int main(void) {
printf("Max: %d\n", MAX);
return 0;
}
/* After preprocessing (what compiler sees): */
/* ... contents of stdio.h ... */
int main(void) {
printf("Max: %d\n", 100); /* MAX replaced */
return 0;
}
/* Preprocessing stages:
1. Trigraph replacement (rarely used)
2. Line splicing (backslash-newline)
3. Tokenization
4. Macro expansion and directive processing
5. String literal concatenation
6. Output pure C code
*/
/* View preprocessor output */
/* gcc -E source.c -o preprocessed.c */
/* Preprocessor directives (start with #) */
/*
#include - Include files
#define - Define macros/constants
#undef - Undefine macros
#if - Conditional compilation
#ifdef - If defined
#ifndef - If not defined
#elif - Else if
#else - Else
#endif - End if
#error - Generate error
#warning - Generate warning
#pragma - Compiler-specific
#line - Change line number
*/#include - File Inclusion
#include inserts file contents at that location. Use <file.h> for system headers (searches system paths) and "file.h" for local headers (searches current directory first). Prevents redefinition with include guards or #pragma once.
/* System headers (angle brackets) */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* Searches system include directories:
/usr/include
/usr/local/include
etc.
*/
/* Local headers (quotes) */
#include "myheader.h"
#include "utils/helper.h"
/* Searches:
1. Current directory
2. Directories specified with -I
3. System directories
*/
/* Relative paths */
#include "../common/types.h"
#include "./config.h"
/* Include guards (prevent multiple inclusion) */
/* myheader.h */
#ifndef MYHEADER_H
#define MYHEADER_H
/* Header contents */
void my_function(void);
#endif /* MYHEADER_H */
/* Alternative: #pragma once */
#pragma once /* Non-standard but widely supported */
void my_function(void);
/* Nested includes work */
/* main.c includes header.h, header.h includes types.h */
/* Circular includes cause problems */
/* a.h includes b.h, b.h includes a.h - BAD! */
/* Use forward declarations or restructure */
/* Common include patterns */
/* In .h file: */
#ifndef MODULE_H
#define MODULE_H
/* Includes needed by header */
#include <stddef.h>
/* Declarations */
typedef struct Module Module;
void module_init(Module *m);
#endif
/* In .c file: */
#include "module.h"
#include <stdio.h>
#include <stdlib.h>
/* Implementation */
struct Module {
int data;
};
void module_init(Module *m) {
m->data = 0;
}
/* Include order matters */
/* System headers first, then local */
#include <stdio.h> /* System */
#include <stdlib.h> /* System */
#include <string.h> /* System */
/* Blank line */
#include "myheader.h" /* Local */
#include "utils.h" /* Local */#define - Constant Definitions
#define creates symbolic constants and simple macros. Constants improve readability and maintainability. Prefer const or enum for type-safe constants, but #define works for preprocessor conditions. No semicolon after #define.
/* Simple constants */
#define MAX_SIZE 100
#define PI 3.14159
#define VERSION "1.0.0"
/* Usage */
int array[MAX_SIZE];
double circumference = 2 * PI * radius;
printf("Version: %s\n", VERSION);
/* After preprocessing: */
int array[100];
double circumference = 2 * 3.14159 * radius;
printf("Version: %s\n", "1.0.0");
/* Multiple lines (use backslash) */
#define LONG_STRING \
"This is a very long string " \
"that spans multiple lines"
/* Numeric expressions */
#define KB 1024
#define MB (KB * 1024)
#define GB (MB * 1024)
/* Always use parentheses */
#define BAD_SQUARE(x) x * x
#define GOOD_SQUARE(x) ((x) * (x))
int a = BAD_SQUARE(2 + 3); /* 2 + 3 * 2 + 3 = 11 (WRONG) */
int b = GOOD_SQUARE(2 + 3); /* ((2 + 3) * (2 + 3)) = 25 (RIGHT) */
/* String constants */
#define PROJECT_NAME "MyProject"
#define ERROR_PREFIX "ERROR: "
#define DEBUG_FORMAT "[DEBUG] %s:%d: "
/* Conditional definitions */
#ifndef BUFFER_SIZE
#define BUFFER_SIZE 1024 /* Default if not defined */
#endif
/* Undefine */
#define TEMP 42
int x = TEMP; /* 42 */
#undef TEMP
/* TEMP no longer defined */
/* Predefined macros (compiler provides) */
__FILE__ /* Current filename */
__LINE__ /* Current line number */
__DATE__ /* Compilation date "Jan 1 2024" */
__TIME__ /* Compilation time "12:34:56" */
__STDC__ /* 1 if conforming to C standard */
/* Usage */
#define DEBUG_LOG(msg) \
printf("%s:%d: %s\n", __FILE__, __LINE__, msg)
DEBUG_LOG("Something happened");
/* Output: file.c:42: Something happened */
/* Compiler-specific */
#ifdef __GNUC__
/* GCC-specific code */
#endif
#ifdef _MSC_VER
/* MSVC-specific code */
#endif
/* Platform detection */
#ifdef _WIN32
#define OS_WINDOWS
#elif defined(__linux__)
#define OS_LINUX
#elif defined(__APPLE__)
#define OS_MAC
#endif
/* Boolean-like constants */
#define TRUE 1
#define FALSE 0
#define SUCCESS 0
#define FAILURE -1
/* Bit flags */
#define FLAG_NONE 0x00
#define FLAG_READ 0x01
#define FLAG_WRITE 0x02
#define FLAG_EXEC 0x04
/* Type aliases (better to use typedef) */
#define BYTE unsigned char
#define UINT unsigned int
/* But typedef is better: */
typedef unsigned char byte_t;
typedef unsigned int uint_t;Macros - Function-like Definitions
Function-like macros perform text substitution with arguments. Fast (no function call overhead) but dangerous (no type checking, side effects). Use inline functions or const when possible. Always parenthesize macro arguments and entire expression.
/* Simple macros */
#define SQUARE(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define ABS(x) ((x) < 0 ? -(x) : (x))
int a = SQUARE(5); /* ((5) * (5)) = 25 */
int b = MAX(10, 20); /* ((10) > (20) ? (10) : (20)) = 20 */
/* Multiple statements (use do-while(0)) */
#define SWAP(a, b) do { \
typeof(a) temp = (a); \
(a) = (b); \
(b) = temp; \
} while(0)
/* Usage */
SWAP(x, y); /* OK */
if (condition)
SWAP(x, y); /* Works correctly */
/* Side effects danger */
#define INCREMENT(x) ((x) + 1)
int i = 5;
int j = INCREMENT(i++); /* (i++) + 1 - i incremented twice! */
/* Better: */
inline int increment(int x) {
return x + 1;
}
/* Stringification (#) */
#define TO_STRING(x) #x
const char *name = TO_STRING(hello); /* "hello" */
const char *expr = TO_STRING(1 + 2); /* "1 + 2" (not "3") */
/* Token pasting (##) */
#define CONCAT(a, b) a##b
int CONCAT(var, 123) = 42; /* int var123 = 42; */
/* Variadic macros (C99) */
#define LOG(format, ...) \
printf("[LOG] " format "\n", __VA_ARGS__)
LOG("Value: %d", 42);
/* Expands to: printf("[LOG] " "Value: %d" "\n", 42); */
/* Safer variadic (GNU extension) */
#define LOG_GNU(format, ...) \
printf("[LOG] " format "\n", ##__VA_ARGS__)
LOG_GNU("No args"); /* Works even without varargs */
/* Debug macros */
#ifdef DEBUG
#define DBG_PRINT(msg) printf("DEBUG: %s\n", msg)
#define DBG_VAR(var) printf(#var " = %d\n", var)
#else
#define DBG_PRINT(msg)
#define DBG_VAR(var)
#endif
/* Usage */
DBG_PRINT("Starting");
int count = 10;
DBG_VAR(count); /* Prints: count = 10 (only in DEBUG) */
/* Assert macro */
#ifdef NDEBUG
#define ASSERT(condition) ((void)0)
#else
#define ASSERT(condition) \
if (!(condition)) { \
fprintf(stderr, "Assertion failed: %s, file %s, line %d\n", \
#condition, __FILE__, __LINE__); \
abort(); \
}
#endif
/* Array size */
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
int numbers[] = {1, 2, 3, 4, 5};
size_t count = ARRAY_SIZE(numbers); /* 5 */
/* Offset of structure member */
#define OFFSETOF(type, member) \
((size_t)&((type*)0)->member)
/* Container of (get structure from member pointer) */
#define CONTAINER_OF(ptr, type, member) \
((type*)((char*)(ptr) - OFFSETOF(type, member)))
/* Min/Max with type safety (GCC) */
#define MIN_SAFE(a, b) ({ \
typeof(a) _a = (a); \
typeof(b) _b = (b); \
_a < _b ? _a : _b; \
})
/* Common pitfalls */
/* 1. Missing parentheses */
#define BAD_ADD(a, b) a + b
int x = BAD_ADD(1, 2) * 3; /* 1 + 2 * 3 = 7, not 9 */
#define GOOD_ADD(a, b) ((a) + (b))
int y = GOOD_ADD(1, 2) * 3; /* (1 + 2) * 3 = 9 */
/* 2. Side effects */
#define BAD_MAX(a, b) ((a) > (b) ? (a) : (b))
int z = BAD_MAX(i++, j++); /* i or j incremented twice */
/* 3. Multiple statements without do-while */
#define BAD_MACRO(x) x++; printf("%d\n", x)
if (condition)
BAD_MACRO(i); /* Only i++ in if, printf always runs */
/* When to use macros */
/*
Use macros for:
- Compile-time constants
- Conditional compilation
- Code generation
- Header guards
Avoid for:
- Regular functions (use inline instead)
- Type definitions (use typedef)
- Constants (use const or enum)
*/Conditional Compilation
Conditional compilation includes/excludes code based on preprocessor conditions. Essential for platform-specific code, debug builds, feature flags. Use #ifdef, #ifndef, #if, #elif, #else, #endif. Enables single codebase for multiple targets.
/* Basic conditionals */
/* #ifdef - if defined */
#ifdef DEBUG
printf("Debug mode\n");
#endif
/* #ifndef - if not defined */
#ifndef RELEASE
/* Development code */
#endif
/* #if - conditional expression */
#if VERSION >= 2
/* Version 2+ code */
#endif
/* #if defined */
#if defined(LINUX) || defined(UNIX)
/* Unix-like systems */
#endif
/* #elif and #else */
#ifdef WINDOWS
#include <windows.h>
#define PATH_SEP '\\'
#elif defined(LINUX)
#include <unistd.h>
#define PATH_SEP '/'
#else
#error "Unsupported platform"
#endif
/* Numeric comparisons */
#define API_VERSION 3
#if API_VERSION < 2
/* Old API */
#elif API_VERSION < 4
/* Current API */
#else
/* Future API */
#endif
/* Logical operators */
#if defined(FEATURE_A) && defined(FEATURE_B)
/* Both features enabled */
#endif
#if !defined(NDEBUG)
/* Debug mode (NDEBUG not defined) */
#endif
/* Feature flags */
#ifdef ENABLE_LOGGING
#define LOG(msg) printf("[LOG] %s\n", msg)
#else
#define LOG(msg) ((void)0)
#endif
/* Platform-specific code */
#ifdef _WIN32
void windows_specific(void) {
/* Windows code */
}
#elif defined(__linux__)
void linux_specific(void) {
/* Linux code */
}
#elif defined(__APPLE__)
void mac_specific(void) {
/* macOS code */
}
#endif
/* Debug vs Release */
#ifdef DEBUG
#define TRACE(x) printf("TRACE: %s\n", #x); x
#define ASSERT(cond) if (!(cond)) abort()
#else
#define TRACE(x) x
#define ASSERT(cond) ((void)0)
#endif
/* Compiler version checks */
#if defined(__GNUC__) && (__GNUC__ >= 4)
/* GCC 4.0 or later */
#define DEPRECATED __attribute__((deprecated))
#elif defined(_MSC_VER) && (_MSC_VER >= 1400)
/* MSVC 2005 or later */
#define DEPRECATED __declspec(deprecated)
#else
#define DEPRECATED
#endif
/* C standard version */
#if __STDC_VERSION__ >= 199901L
/* C99 or later */
#define INLINE inline
#else
/* C89 */
#define INLINE
#endif
/* Architecture-specific */
#if defined(__x86_64__) || defined(_M_X64)
/* 64-bit x86 */
#define POINTER_SIZE 8
#elif defined(__i386__) || defined(_M_IX86)
/* 32-bit x86 */
#define POINTER_SIZE 4
#endif
/* Optimization level */
#ifdef __OPTIMIZE__
/* Optimizations enabled */
#define ALWAYS_INLINE __attribute__((always_inline))
#else
#define ALWAYS_INLINE
#endif
/* Testing */
#ifdef TESTING
/* Expose internals for testing */
#define STATIC
#else
#define STATIC static
#endif
/* Example: Multi-platform sleep */
#ifdef _WIN32
#include <windows.h>
#define SLEEP_MS(ms) Sleep(ms)
#else
#include <unistd.h>
#define SLEEP_MS(ms) usleep((ms) * 1000)
#endif
void wait_one_second(void) {
SLEEP_MS(1000);
}
/* Configuration */
/* config.h */
#ifndef CONFIG_H
#define CONFIG_H
/* User-configurable options */
#define MAX_CONNECTIONS 100
#define ENABLE_SSL
/* #define DEBUG_MODE */ /* Commented out */
#ifdef ENABLE_SSL
#define USE_OPENSSL 1
#else
#define USE_OPENSSL 0
#endif
#endif
/* Build configurations */
/* Compile with: gcc -DDEBUG -DVERSION=2 file.c */
#ifndef VERSION
#define VERSION 1 /* Default */
#endif
#if VERSION == 1
/* Version 1 code */
#elif VERSION == 2
/* Version 2 code */
#endifSummary & What's Next
Key Takeaways:
- ✅ Preprocessor runs before compilation
- ✅ #include inserts file contents
- ✅ #define creates constants and macros
- ✅ Always parenthesize macro arguments
- ✅ Macros have no type checking
- ✅ Conditional compilation with #ifdef, #if
- ✅ Use include guards to prevent multiple inclusion
- ✅ Predefined macros: __FILE__, __LINE__, __DATE__