C语言预处理完全指南

语法基础

预处理概述

预处理是 C 编译流程的第一步,负责处理以 # 开头的预处理指令。预处理在编译前完成文本替换、条件选择等工作,是 C 语言强大而独特的特性。

宏定义

宏定义使用 #define 指令,可以定义常量、函数宏、类型别名等:

// 常量宏
#define PI 3.1415926
#define MAX_SIZE 1024

// 函数宏(带参数)
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define SWAP(a, b) do { typeof(a) _t = a; a = b; b = _t; } while(0)

// 避免重复计算,使用 typeof 获取类型
#define MIN(a, b) ({ \
    typeof(a) _a = (a); \
    typeof(b) _b = (b); \
    _a < _b ? _a : _b; \
})

// 字符串化:# 将参数转为字符串
#define PRINT_INT(x) printf(#x " = %d\n", x)
// PRINT_INT(val) 展开为 printf("val = %d\n", val)

// 连接符:## 拼接标识符
#define DECLARE_GETTER(type, name) \
    type get_##name(void) { return name; }
// DECLARE_GETTER(int, count) 展开为 get_count 函数

条件编译

条件编译根据条件选择性地编译代码,常用于跨平台兼容、调试开关等:

// 根据平台选择代码
#ifdef __linux__
    // Linux 平台代码
    #define PATH_SEP '/'
#elif _WIN32
    // Windows 平台代码
    #define PATH_SEP '\\'
#endif

// 调试开关
#ifdef DEBUG
    printf("Debug: x = %d\n", x);
#endif

// 防止头文件重复包含
#ifndef MY_HEADER_H
#define MY_HEADER_H
    // 头文件内容
#endif

// 编译时断言(C11 后也可用 _Static_assert)
#define COMPILE_ASSERT(expr) \
    typedef int static_assert_[(expr) ? 1 : -1]

预定义宏

C 标准定义了一些有用的预定义宏:

__FILE__      // 当前源文件名(字符串)
__LINE__      // 当前行号(整数)
__DATE__      // 编译日期(字符串)
__TIME__      // 编译时间(字符串)
__STDC__      // 是否符合 ANSI C 标准
__STDC_VERSION__  // C 标准版本(如 201112L 表示 C11)

文件包含

#include 指令用于包含其他文件内容:

// 尖括号:系统头文件,编译器从系统路径搜索
#include 
#include 

// 双引号:用户头文件,先从当前目录搜索
#include "myheader.h"
#include "subdir/config.h"

// 防止重复包含的完整写法
#ifndef MY_HEADER_H
#define MY_HEADER_H
#endif

宏与函数的区别

宏是文本替换,没有函数调用开销,但可能产生副作用:

// 风险:参数被求值多次
#define SQUARE(x) ((x) * (x))
SQUARE(++i);  // 展开为 ((++i) * (++i)),i 被递增两次!

// 安全写法:使用 GNU C 扩展 typeof
#define SQUARE(x) ({ typeof(x) _x = (x); _x * _x; })

// 或者直接使用内联函数(C99+)
static inline int square(int x) { return x * x; }

常见应用场景

1. 跨平台代码:根据操作系统、编译器选择不同实现

2. 性能关键代码:用宏替代函数调用减少开销

3. 代码生成:减少重复代码,如创建 getter/setter

4. 调试与日志:根据调试开关选择是否输出调试信息

5. 版本控制:根据功能开关选择编译不同模块