《笨办法学C》笔记之C预处理器
预处理(或称预编译)是指在进行编译的第一遍扫描(词法扫描和语法分析)之前所作的工作。预处理指令指示在程序正式编译前就由编译器进行的操作,可放在程序中任何位置。
预处理是C语言的一个重要功能,它由预处理程序负责完成。当对一个源文件进行编译时,系统将自动引用预处理程序对源程序中的预处理部分作处理,处理完毕自动进入对源程序的编译。
C语言的预处理器主要可以做到:
- 文件包含
- 条件编译
- 宏定义
合理使用预处理功能编写的程序便于阅读、修改、移植和调试,也有利于模块化程序设计。
序号 | 指令 | 描述 |
---|---|---|
1 | #define | 定义宏 |
2 | #include | 包含一个源代码文件 |
3 | #undef | 取消已定义的宏 |
4 | #ifdef | 如果宏已经定义,则返回真 |
5 | #ifndef | 如果宏没有定义,则返回真 |
6 | #if | 如果给定条件为真,则编译下面代码 |
7 | #else | #if 的替代方案 |
8 | #elif | 如果前面的 #if 给定条件不为真,当前条件为真,则编译下面代码 |
9 | #endif | 结束一个 #if……#else 条件编译块 |
10 | #error | 当遇到标准错误时,输出错误消息 |
11 | #pragma | 使用标准化方法,向编译器发布特殊的命令到编译器中 |
文件包含
C语言使用#include
标识符,来表明.c文件内将要调用.h头文件所定义的数据结构或函数。
#include <stdio.h>
条件编译
C语言的预处理器,通过前表中第2-9项指令能够做到:对.c文件中的代码或者对.h文件中的定义有选择的编译。
其编译的条件是:首先满足宏定义的条件,然后才会执行编译。
#ifdef XXX
/* <C code> */
#endif
如果编译器读取到这段代码的时候,XXX
已经定义了,那么<C code>
也会得到编译,否则编译器就会忽略这段代码,直到#endif
位置。
宏定义
预定义宏
ANSI C 定义了许多宏。在编程中可以使用这些宏,但是不能直接修改这些预定义的宏。
宏 | 描述 |
---|---|
DATE | 当前日期,一个以 "MMM DD YYYY" 格式表示的字符常量。 |
TIME | 当前时间,一个以 "HH:MM:SS" 格式表示的字符常量。 |
FILE | 这会包含当前文件名,一个字符串常量。 |
LINE | 这会包含当前行号,一个十进制常量。 |
STDC | 当编译器以 ANSI 标准编译时,则定义为 1。 |
# include <stdio.h>
main()
{
printf("File :%s\n", __FILE__ );
printf("Date :%s\n", __DATE__ );
printf("Time :%s\n", __TIME__ );
printf("Line :%d\n", __LINE__ );
printf("ANSI :%d\n", __STDC__ );
}
当上面的代码(在文件 test.c 中)被编译和执行时,它会产生下列结果:
File :test.c
Date :Jun 2 2012
Time :03:36:24
Line :8
ANSI :1
自定义宏
常量定义
最简单的宏定义就是常量定义。
#define CONST 123
#define MESSAGE "This is a pre-defined msg"
这句定义就指定后文中的CONST
都要被替换为123
。如果遇到#undef CONST
,那后面的CONST
就不会被这样替换了。
需要注意的是,宏定义的常量没有占用内存。编译器会在编译后面的代码中,直接将所有CONST
替换为123
,将所有MESSAGE
替换为"This is a pre-defined msg"
。编译后的二进制可执行文件不会意识到CONST
和MESSAGE
的存在。因为,这些定义过的宏在编译的第一步——“预处理”中就被替换完毕了。
函数模版
C预处理器还能够预定义函数模版。例如:
#define MAX(x,y) ((x) > (y) ? (x) : (y))
int main() {
int i = 12;
int j = 33;
int res = MAX(i, j);
return res;
}
上例中,i,j被带入MAX宏中进行计算,res存储着计算结果。
这和函数调用很像,然而两者并不相同。
编译器在预处理阶段,直接将预定义的函数模版在代码中展开。在二进制文件执行过程中,并不存在函数调用过程中的栈帧调度、参数传递以及结果回传等等,也不会占用内存空间。如果去查看等价的汇编代码,也不会发现call指令。因为预处理阶段结束后,函数模版在嵌入实际参数之后,成为代码的一部分,而不是函数形式。
小小总结
显而易见,宏定义不占用内存空间,不涉及函数的栈帧切换,在性能方面有很强的优势。
但是,宏定义也有严重的问题。那就是这里面没有任何类型检查。编译器会无条件的直接做替换,而如果存在类型不匹配,只有在编译时替换完成后才能发现。