第一,编译器首先把源代码中出现的字符映射到源字符集。
第二,编译器查找反斜线后紧跟换行符的实例并删除这些实例。
第三,编译器将文本划分成预处理的语言符号序列和空白字符及注释序列(术语语言符号代表由空格分隔的组。本章后面部分将详细讨论语言符号)。应注意的一点是编译器用一个空格字符代替每一个注释。
C实现可能还会选用单个空格代替每一个空白字符(不包括换行符)序列。最后,程序进入预处理阶段。预处理器寻找可能存在的预处理指令。这些指令由一行开始处的#符号标识。
预处理指令从#开始,到最后第一个换行符为止。也就是说,指令的长度限于一行代码。但是正如前文提到的,在预处理开始前,系统会删除反斜线和换行符的组合。因此可以把指令扩张到几个物理行,由这些物理行组成单个逻辑航。
每个#define行(即逻辑行)由三部分组成。第一部分为指令#define自身(预处理指令)。第二部分为所选择的缩略语,这些缩略语称为宏(macro),有类对象宏(object-like macro)和类函数宏。宏的名字中不允许有空格,而且必须遵循C变量命名规则:只能使用字母、数字和下划线(_),第一个字符不能为数字。第三部分称为替换列表(replacement list)和主体。预处理器在程序中发现了宏的实例后,总会用实体代替该宏(有一种例外,以后进行说明)。从宏变成最终的替换文本的过程称为宏展开。注意,可以使用标准的C注释方法在#define行中进行注释。正如前面提到的,在预处理器处理之前,每个注释都会被一个空格所代替。
1 #include <stdio.h> 2 3 #define CON(x) "hello" # x 4 #define CON2(x, y) x # y 5 6 #define CONARG(x) hello ## x 7 #define CONARG2(x, y) x ## y 8 9 int main(void) 10 { 11 12 // # 预处理运算符前面操作数必须为字符串 13 printf("%s\n", CON(world)); 14 printf("%s\n", CON2("hello", world)); 15 16 printf("----------------------\n"); 17 18 // ## 19 int helloworld = 123; 20 printf("helloworld = %d\n", CONARG(world)); 21 printf("helloworld = %d\n", CONARG2(hello, world)); 22 }
[root@playstation basic]# ./contact helloworld helloworld ---------------------- helloworld = 123 helloworld = 123 [root@playstation basic]#
1 #include <stdio.h> 2 3 #define PR(...) printf(__VA_ARGS__) 4 5 int main(void) 6 { 7 int id = 20092192; 8 char name[] = "helloworld"; 9 float height = 176.8; 10 11 PR("id = %d\n", id); 12 PR("id = %d, name = %s\n", id, name); 13 PR("id = %d, name = %s, height = %f\n", id, name, height); 14 }
1 #include<stdio.h> 2 3 int main(void) 4 { 5 int SIZE = 3; 6 7 #define SIZE 1024 8 9 printf("%d\n", SIZE); 10 11 }
如果标识符不是宏,而是(例如)一个具有文件作用域的C变量,那么预处理器把标识符当做未定义的。
许多新的实现提供另一种方法来判断一个名字是否已经定义。不需使用:
#ifdef VAX
而是采用下面的形式:
#if defined(VAX)
这里,defined是一个预处理器运算符。如果defined的参数已用#define定义过,那么defined返回1;否则返回0。这种新方法的优点在于它可以和#elif一起使用。
#if defined(IBMPC)
#include "ibmpc.h"
#elif defined(VAX)
#include "vax.h"
#elif defined(MAC)
#include "mac.h"
#endif
C99标准提供一个名为__func__的预定义标识符。__func__展开为一个代表函数名(该函数包含该标识符)的字符串。该标识符具有函数作用域,而宏本质上具有文件作用域。因而__func__是C语言的预定义标识符,而非预定义宏。
#line指令用于重置由__LINE__和__FILE__宏报告的行号和文件名。可以这样使用#line:
#line 1000 // 把当前行号重置为1000
#line 10 "cool.c" // 把行号重置为10, 文件名重置为cool.c
#error指令使预编译器发出一条错误消息,该消息包含指令中的文本。可能的话,编译过程应该中断。
在现代的编译器中,可用命令行参数或IDE菜单修改编译器的某些设置。也可用#pragma将编译器指令置于源代码中。
例如,在开发C99时,用C9X代表C99。编译器可以使用下面的编译指示(prama)来启用对C9X的支持:
#pragma c9x on
编译器在优化内联函数时,必须知道函数定义的内容。这意味着内联函数的定义和对该函数的调用必须在同一文件中。正因为这样,内联函数通常具有内部链接。因此,在多文件程序中,每个调用内联函数的文件都要对函数进行定义。达到这个目标的最简单方法为:在头文件中定义内联函数,并在使用该函数的文件中包含该头文件。一般不在头文件中放置可执行代码,但内联函数是个例外。因为内联函数具有内部链接,所以在多个文件中定义同一内联函数不会产生什么问题。