C 语言预处理器的工作原理

本文内容

目录汇总:C 语言零基础入门教程

预处理器的行为是由预处理指令(由 # 字符开头的一些命令)控制的。我们已经在前面的 C 语言零基础入门教程 中遇见过其中两种指令,即 #define#include

预处理器

#define 指令定义了一个——用来代表其他东西的一个名字,例如常量或常用的表达式。预处理器会通过将宏的名字和它的定义存储在一起来响应 #define 指令。当这个宏在后面的程序中使用到时,预处理器“扩展”宏,将宏替换为其定义内容。

#include 指令告诉预处理器打开一个特定的文件,将它的内容作为正在编译的文件的一部分“包含”进来。例如,代码行

1
#include <stdio.h>

指示预处理器打开一个名为 stdio.h 的文件,并将它的内容加到当前的程序中。(stdio.h 包含了 C 语言标准输入/输出函数的原型。)

上图说明了预处理器在编译过程中的作用。预处理器的输入是一个 C 语言程序,程序可能包含指令。预处理器会执行这些指令,并在处理过程中删除这些指令。预处理器的输出是另一个 C 程序:原程序编辑后的版本,不再包含指令。预处理器的输出被直接交给编译器,编译器检查程序是否有错误,并将程序翻译为目标代码(机器指令)。

为了展现预处理器的作用,我们将它应用于 C 语言定义常量的名字 的程序 celsius.c。下面是原来的程序:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
/* Converts a Fahrenheit temperature to Celsius */

#include <stdio.h>

#define FREEZING_PT 32.0f
#define SCALE_FACTOR (5.0f / 9.0f)

int main(void)
{
  float fahrenheit, celsius;

  printf("Enter Fahrenheit temperature: ");
  scanf("%f", &fahrenheit);

  celsius = (fahrenheit - FREEZING_PT) * SCALE_FACTOR;

  printf("Celsius equivalent is: %.1f\n", celsius);

  return 0;
}

预处理结束后,程序是下面的样子:

空行
空行
从stdio.h中引入的行
空行
空行
空行
空行
int main(void)
{
  float fahrenheit, celsius;

  printf("Enter Fahrenheit temperature:  ");
  scanf("%f", &fahrenheit);

  celsius = (fahrenheit - 32.0f) * (5.0f / 9.0f);

  printf("Celsius equivalent is: %.1f\n", celsius);

  return 0;
}

预处理器通过引入 stdio.h 的内容来响应 #include 指令。预处理器也删除了 #define 指令,并且替换了该文件中稍后出现在任何位置上的 FREEZING_PTSCALE_FACTOR。请注意预处理器并没有删除包含指令的行,而是简单地将它们替换为空。

正如这个例子所展示的那样,预处理器不仅仅执行了指令,还做了一些其他的事情。特别值得注意的是,它将每一处注释都替换为一个空格字符。有一些预处理器还会进一步删除不必要的空白字符,包括每一行开始用于缩进的空格符和制表符。

在 C 语言较早的时期,预处理器是一个单独的程序,它的输出提供给编译器。如今,预处理器通常和编译器集成在一起,而且其输出也不一定全是 C 代码(例如,包含 <stdio.h> 之类的标准头使得我们可以在程序中使用相应头中的函数,而不需要把头的内容复制到程序的源代码中)。然而,将预处理器和编译器看作不同的程序仍然是有用的。实际上,大部分 C 编译器提供了一种方法,使用户可以看到预处理器的输出。在指定某个特定的选项(GCC 用的是 -E)时编译器会产生预处理器的输出。其他一些编译器会提供一个类似于集成的预处理器的独立程序。要了解更多的信息,可以查看你使用的编译器的文档。

注意,预处理器仅知道少量 C 语言的规则。因此,它在执行指令时非常有可能产生非法的程序。经常是原始程序看起来没问题,使错误查找起来很难。对于较复杂的程序,检查预处理器的输出可能是找到这类错误的有效途径。

请参阅

(完)

comments powered by Disqus

本文内容