4.11 The C Preprocessor
C provides certain language facilities by means of a preprocessor, which is conceptionally a separate first step in compilation. The two most frequently used features are #include, to include the contents of a file during compilation, and #define, to replace a token by an arbitrary sequence of characters. Other features described in this section include conditional compilation and macros with arguments.
C语言通过预处理器提供了一些语言功能。从概念上讲,预处理器是编译过程中单独执行的第一个步骤。两个最常用的预处理器指令是:#include 指令(用于在编译期间把指定文件的内容包含进当前文件中)和#define指令(用任意字符序列替代一个标记)。本节还将介绍预处理器的其它一些特性,如条件编译与带参数的宏。
4.11.1 File Inclusion
File inclusion makes it easy to handle collections of #defines and declarations (among other things). Any source line of the form
文件包含指令(即#include指令)使得处理大量的#define指令以及声明更加方便。在源文件中,任何形如:
#include "filename"
or
#include <filename>
is replaced by the contents of the file filename. If the filename is quoted, searching for the file typically begins where the source program was found; if it is not found there, or if the name is enclosed in < and >, searching follows an implementation-defined rule to find the file. An included file may itself contain #include lines.
的行都将被替换为由文件名指定的文件的内容。如果文件名用引号引起来,则在源文件所在位置查找该文件;如果在该位置没有找到文件,或者如果文件名是用尖括号<与>括起来的,则将根据相应的规则查找该文件,这个规则同具体的实现有关。被包含的文件本身也可包含#include指令。
There are often several #include lines at the beginning of a source file, to include common #define statements and extern declarations, or to access the function prototype declarations for library functions from headers like <stdio.h>. (Strictly speaking, these need not be files; the details of how headers are accessed are implementation-dependent.)
源文件的开始处通常都会有多个#include指令,它们用以包含常见的#define语句和extern声明,或从头文件中访问库函数的函数原型声明,比如<stdio.h>。(严格地说,这些内容没有必要单独存放在文件中;访问头文件的细节同具体的实现有关。)
#include is the preferred way to tie the declarations together for a large program. It guarantees that all the source files will be supplied with the same definitions and variable declarations, and thus eliminates a particularly nasty kind of bug. Naturally, when an included file is changed, all files that depend on it must be recompiled.
在大的程序中,#include 指令是将所有声明捆绑在一起的较好的方法。它保证所有的源文件都具有相同的定义与变量声明,这样可以避免出现一些不必要的错误。很自然,如果某个包含文件的内容发生了变化,那么所有依赖于该包含文件的源文件都必须重新编译。
4.11.2 Macro Substitution(宏替换)
A definition has the form
#define name replacement text
It calls for a macro substitution of the simplest kind - subsequent occurrences of the token name will be replaced by the replacement text. The name in a #define has the same form as a variable name; the replacement text is arbitrary. Normally the replacement text is the rest of the line, but a long definition may be continued onto several lines by placing a \ at the end of each line to be continued. The scope of a name defined with #define is from its point of definition to the end of the source file being compiled. A definition may use previous definitions. Substitutions are made only for tokens, and do not take place within quoted strings. For example, if YES is a defined name, there would be no substitution in printf("YES") or in YESMAN.
这是一种最简单的宏替换——后续所有出现名字记号的地方都将被替换为替换文本。#define指令中的名字与变量名的命名方式相同,替换文本可以是任意字符串。通常情况下,#define指令占一行,替换文本是#define指令行尾部的所有剩余部分内容,但也可以把一个较长的宏定义分成若干行,这时需要在待续的行末尾加上一个反斜杠符\。#define 指令定义的名字的作用域从其定义点开始,到被编译的源文件的末尾处结束。宏定义中也可以使用前面出现的宏定义。替换只对记号进行,对括在引号中的字符串不起作用。例如,如果YES是一个通过#define指令定义过的名字,则在printf("YES")或YESMAN中将不执行替换。
Any name may be defined with any replacement text. For example
#define forever for (;;) /* infinite loop */
defines a new word, forever, for an infinite loop.
It is also possible to define macros with arguments, so the replacement text can be different for different calls of the macro. As an example, define a macro called max:
宏定义也可以带参数,这样可以对不同的宏调用使用不同的替换文本。例如,下列宏定义定义了一个宏max:
#define max(A, B) ((A) > (B) ? (A) : (B))
Although it looks like a function call, a use of max expands into in-line code. Each occurrence of a formal parameter (here A or B) will be replaced by the corresponding actual argument. Thus the line
使用宏max 看起来很像是函数词用,但宏调用直接将替换文本插入到代码中。形式参数(在此为A或B)的每次出现都将被替换成对应的实际参数。因此,语句:
x = max(p+q, r+s);
will be replaced by the line
x = ((p+q) > (r+s) ? (p+q) : (r+s));
So long as the arguments are treated consistently, this macro will serve for any data type; there is no need for different kinds of max for different data types, as there would be with functions.
如果对各种类型的参数的处理是一致的,则可以将同一个宏定义应用于任何数据类型,而无需针对不同的数据类型需要定义不同的max函数。
If you examine the expansion of max, you will notice some pitfalls. The expressions are evaluated twice; this is bad if they involve side effects like increment operators or input and output. For instance
仔细考虑一下max 的展开式,就会发现它存在一些缺陷。其中,作为参数的表达式要重复计算两次,如果表达式存在副作用(比如含有自增运算符或输入/输出),则会出现不正确的情况。例如:
max(i++, j++) /* WRONG */
will increment the larger twice. Some care also has to be taken with parentheses to make sure the order of evaluation is preserved; consider what happens when the macro
它将对每个参数执行两次自增操作。同时还必须注意,要适当使用圆括号以保证计算次序的正确性。考虑下列宏定义:
#define square(x) x * x /* WRONG */
is invoked as square(z+1).
Nonetheless, macros are valuable. One practical example comes from <stdio.h>, in which getchar and putchar are often defined as macros to avoid the run-time overhead of a function call per character processed. The functions in <ctype.h> are also usually implemented as macros.
但是,宏还是很有价值的。<stdio.h>头文件中有一个很实用的例子:getchar 与putchar 函数在实际中常常被定义为宏,这样可以避免处理字符时调用函数所需的运行时开销。<ctype.h>头文件中定义的函数也常常是通过宏实现的。
Names may be undefined with #undef, usually to ensure that a routine is really a function, not a macro:
可以通过#undef指令取消名字的宏定义,这样做可以保证后续的调用是函数调用,而不是宏调用:
#undef getchar
int getchar(void) { ... }
Formal parameters are not replaced within quoted strings. If, however, a parameter name is preceded by a # in the replacement text, the combination will be expanded into a quoted string with the parameter replaced by the actual argument. This can be combined with string concatenation to make, for example, a debugging print macro:
形式参数不能用带引号的字符串替换。但是,如果在替换文本中,参数名以#作为前缀则结果将被扩展为由实际参数替换该参数的带引号的字符串。例如,可以将它与字符串连接运算结合起来编写一个调试打印宏:
#define dprint(expr) printf(#expr " = %g\n", expr)
When this is invoked, as in
dprint(x/y)
the macro is expanded into
printf("x/y" " = &g\n", x/y);
and the strings are concatenated, so the effect is
printf("x/y = &g\n", x/y);
Within the actual argument, each " is replaced by \" and each \ by \\, so the result is a legal string constant.
在实际参数中,每个双引号"将被替换为\",反斜杠\将被替换为\\,因此替换后的字符串是合法的字符串常量。
The preprocessor operator ## provides a way to concatenate actual arguments during macro expansion. If a parameter in the replacement text is adjacent to a ##, the parameter is replaced by the actual argument, the ## and surrounding white space are removed, and the result is re-scanned. For example, the macro paste concatenates its two arguments:
预处理器运算符##为宏扩展提供了一种连接实际参数的手段。如果替换文本中的参数与##相邻,则该参数将被实际参数替换,##与前后的空白符将被删除,并对替换后的结果重新扫描。例如,下面定义的宏paste用于连接两个参数
#define paste(front, back) front ## back
so paste(name, 1) creates the token name1.
因此,宏调用paste(name, 1)的结果将建立记号name1。
The rules for nested uses of ## are arcane; further details may be found in Appendix A.
4.11.3 Conditional Inclusion
It is possible to control preprocessing itself with conditional statements that are evaluated during preprocessing. This provides a way to include code selectively, depending on the value of conditions evaluated during compilation.
还可以使用条件语句对预处理本身进行控制,这种条件语句的值是在预处理执行的过程中进行计算。这种方式为在编译过程中根据计算所得的条件值选择性地包含不同代码提供了一种手段。
The #if line evaluates a constant integer expression (which may not include sizeof, casts, or enum constants). If the expression is non-zero, subsequent lines until an #endif or #elif or #else are included. (The preprocessor statement #elif is like else-if.) The expression defined(name) in a #if is 1 if the name has been defined, and 0 otherwise.
#if语句对其中的常量整型表达式(其中不能包含sizeof、类型转换运算符或enum常量)进行求值,若该表达式的值不等于0,则包含其后的各行,直到遇到#endif、#elif或#else 语句为止(预处理器语句#elif 类似于else if)。在#if 语句中可以使用表达式defined(名字),该表达式的值遵循下列规则:当名字已经定义时,其值为1;否则,其值为0。
For example, to make sure that the contents of a file hdr.h are included only once, the contents of the file are surrounded with a conditional like this:
#if !defined(HDR)
#define HDR
/* contents of hdr.h go here */
#endif
The first inclusion of hdr.h defines the name HDR; subsequent inclusions will find the name defined and skip down to the #endif. A similar style can be used to avoid including files multiple times. If this style is used consistently, then each header can itself include any other headers on which it depends, without the user of the header having to deal with the interdependence.
第一次包含头文件hdr.h时,将定义名字HDR;此后再次包含该头文件时,会发现该名字已经定义,这样将直接跳转到#endif处。类似的方式也可以用来避免多次重复包含同一文件。如果多个头文件能够一致地使用这种方式,那么,每个头文件都可以将它所依赖的任何头文件包含进来,用户不必考虑和处理头文件之间的各种依赖关系。
This sequence tests the name SYSTEM to decide which version of a header to include:
下面的这段预处理代码首先测试系统变量SYSTEM,然后根据该变量的值确定包含哪个版本的头文件:
#if SYSTEM == SYSV
#define HDR "sysv.h"
#elif SYSTEM == BSD
#define HDR "bsd.h"
#elif SYSTEM == MSDOS
#define HDR "msdos.h"
#else
#define HDR "default.h"
#endif
#include HDR
The #ifdef and #ifndef lines are specialized forms that test whether a name is defined. The first example of #if above could have been written
C语言专门定义了两个预处理语句#ifdef与#ifndef,它们用来测试某个名字是否已经定义。上面有关#if的第一个例子可以改写为下列形式:
#ifndef HDR
#define HDR
/* contents of hdr.h go here */
#endif