C语言预处理详解:宏、条件编译与头文件保护

C语言预处理详解

1. 预定义宏

1
2
printf("file=%s line=%d date=%s time=%s\n",
__FILE__, __LINE__, __DATE__, __TIME__);

常用预定义标识包括 __FILE__、__LINE__、__DATE__、__TIME__ 和标准版本相关宏。

2. 对象宏

1
2
#define BUFFER_SIZE 1024
#define PI 3.1415926

宏是预处理阶段的文本替换,没有类型检查和独立作用域。对于有类型的常量,可根据场景考虑 const 或枚举。

3. 函数宏与括号

1
2
#define SQUARE(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))

宏参数和整体都应加括号,否则:

1
2
#define BAD_SQUARE(x) x * x
BAD_SQUARE(1 + 2) // 展开成1 + 2 * 1 + 2

4. 宏参数副作用

1
int m = MAX(i++, j++);

函数宏可能多次计算参数,使自增、函数调用等副作用重复发生。参数有副作用时不要传给会重复展开它的宏,优先使用普通函数或 inline 函数。

5. #与##

# 把宏参数转换为字符串:

1
2
#define PRINT_INT(x) printf(#x " = %d\n", (x))
PRINT_INT(value);

## 连接两个记号:

1
2
#define MAKE_NAME(prefix, number) prefix##number
int MAKE_NAME(value, 1) = 10; // int value1 = 10;

6. 取消宏与命令行定义

1
#undef BUFFER_SIZE

编译时定义宏:

1
gcc -DDEBUG=1 main.c -o app

也可以用 -U 取消某个宏定义。

7. 条件编译

1
2
3
4
5
#ifdef DEBUG
#define LOG(fmt, ...) fprintf(stderr, fmt, __VA_ARGS__)
#else
#define LOG(fmt, ...) ((void)0)
#endif

还可使用 #if、#elif、#else、#endif 和 defined,用于平台适配、功能开关和调试代码。

8. 头文件保护

1
2
3
4
5
6
#ifndef PROJECT_NET_SOCKET_H
#define PROJECT_NET_SOCKET_H

/* 声明 */

#endif

它防止同一翻译单元重复包含头文件。#pragma once 更简洁且被广泛支持,但不属于传统C标准。

#include <file.h> 通常搜索系统路径;#include "file.h" 通常先搜索当前项目相关路径,再搜索系统路径。

9. 宏与函数的选择

宏可以接受不同类型并在预处理期展开,但没有类型检查,可能重复求值且难以调试。普通函数类型明确、只计算参数一次;现代编译器也能内联短函数。除条件编译、常量配置和必要的代码生成外,不应为了省一次调用盲目使用宏。

10. 总结

预处理发生在正式编译之前,本质是指令驱动的文本转换。写宏时要控制括号与副作用,组织头文件时要防止重复包含,并尽量把复杂逻辑留给具有类型检查的C函数。