C和指针——预处理器

C预处理器(preprocessor)在源代码编译之前对其进行一些文本性质的操作。它的主要任务包括删除注释、插入被#include指令包含的文件的内容、定义和替换由#define指令定义的符号以及确定代码的部分内容是否应该根据一些条件编译指令进行编译。

预定义符号:它们的值或者是字符串、或者是十进制数字常量。_FILE_和_LINE_在确认调试输出的来源方面很有用处。_DATE_和_TIME_常常用于在被编译的程序中加入版本信息。_STDC_用于那些在ANSI环境和非ANSI环境都必须进行编译的程序中结合条件编译。

one

#define name stuff。有了这条指令后,每当由符号name出现在这条指令后面时,预处理器就会把它替换成stuff。如果定义中stuff非常长,可以分成几行,除了最后一行之外,每行的末尾都有加上一个反斜杠。

宏:#define机制包括了一个规定,允许把参数替换到文件中,这种实现通常称为宏(macro)或定义宏(define macro)。声明方式如下:

two

其中,parameter-list(参数列表)是一个由逗号分隔的符号列表,它们可能出现在stuff中。参数列表的左括号必须与name紧邻。如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。

当宏被调用时,名字后面是一个由逗号分隔的值的列表,每个值都与宏定义的一个参数相对,整个列表用一对括号包围。当参数出现在程序中时,与每个参数对应的实际值都将被替换到stuff中。宏不可以出现递归。宏和类型无关。宏定义并没有用一个分号结尾,分号出现在调用这个宏的语句中。

当预处理器搜索#define定义的符号时,字符串常量的内容并不进行检查。

奇偶效验是一种错误检查机制。在数据被存储或通过通信线路传送之前,唯一给值计算(并添加)一个校验位,使数据的二进制模式中的1的个数为一个偶数。以后,数据可以通过计算它的1的个数来验证其有效性。如果结果是奇数,那么数据就出现了错误。这个技巧被称为偶校验。奇校验的工作原理相同,只是计算并添加校验位之后,数据的二进制模式中1的个数是奇数。

宏定义(对于绝大多数由#define定义的符号也是如此)一个常见的约定就是把宏名字全部大写。

宏和函数的不同之处:

three

#undef。用于移除一个宏定义。语法如下:

four

如果一个现存的名字需要被重新定义,那么它旧定义首先必须用#undef移除。

命令行定义:许多C编译器提供了一种能力,允许在命令行中定义符号,用于启动编译过程。在UNIX编译器中,-D选项可以完成这项任务。提供符号命令行定义的编译器也提供在命令行中去除符号的定义。在UNIX编译器上,-U选项用于执行这项任务。

条件编译:可以选择代码的一部分是被正常编译还是完全忽略。用于支持条件编译的基本结构是#if指令和其匹配的#endif指令。语法形式如下:

five

其中constant-expression(常量表达式)由预处理器进行求值。如果它的值是非零值(真),那么statements部分就被正常编译,否则预处理器就安静地删除它们。

注:所谓常量表达式,就是说它是字面值常量,或者是一个由#define定义的符号。如果变量在执行器之前无法获得它们的值,那么它们如果出现在常量表达式中就是非法的,因为它们的值在编译时是不可预测的。

条件编译的另一个用途是在编译时选择不同的代码部分。为了支持这个功能,#if指令还具有可选的#elif和#else子句。语法形式如下:

six

#elif子句出现的次数可以不限。每个constant-expression只有当前面所有常量表达式的值都为假时才会被编译。#else子句中的语句只有当前面所有的常量表达式的值都为假时才会被编译,在其他情况下它都会被忽略。

是否被定义:#ifedf symbol 测试一个符号是否已经被定义。该语句与#if defined(symbol)等价。但#if形式功能更强。因为常量表达式可能包含额外的条件。

函数库文件包含:编译器支持两种不同类型的#include文件包含——函数库文件和本地文件。函数库头文件包含使用如下的语法:

seven

对于filename,并不存在任何限制,不过根据约定,标准库文件以一个.h后缀结尾。本地文件包含使用如下语法:

eight

标准允许编译器自行决定是否把本地形式的#include和函数库形式的#include区别对待。处理本地头文件的一种常见策略就是在源文件所在的当前目录进行查找,如果该头文件并未找到,编译器就像查找函数库头文件一样在标准位置查找本地头文件。UNIX系统和Borland C编译器所支持的一种变体形式就是使用绝对路径名,它不仅指定文件的名字,而且指定了文件的位置。UNIX系统中的绝对路径名以一个斜杠开头,在MS-DOS系统中,它所使用的是反斜杠而非斜杠。

嵌套文件包含:标准要求编译器必须支持至少8层的头文件嵌套,但它并没有现代嵌套深度的最大值。

其他指令:预处理器还支持其他一些指令。首先,当程序编译之后,#error指令允许你生成错误信息。语法如下。

night

#line number “string” 指令通知预处理器number是下一行输入的行号。如果给出了可选部分“string” ,预处理器就把它作为当前文件的名字。要注意的是,这条指令将修改_LINE_符号的值。如果加上可选部分,它还将修改_FILE_符号的值。

#program 指令是另一种机制,用于支持因编译器而异的特性。它的语法也是因编译器而异。从本质上说,#program是不可移植的。预处理器将忽略它不认识的#program指令,两个不同的编译器可能以两种不同的方式解释同一条#program指令。

无效指令(null directive)就是一个#符号开头,但后面不跟任何内容的一行。这类指令只是被预处理器简单地删除。

#argument 结构由预处理器转换为字符串常量“argument”。##操作符用于把它两边的文件粘贴成同一个标识符。

#fidef和#ifndef指令可以测试某个符号是否已被定义。