编译器中的预处理到底是什么意思?

8
我正在尝试理解typedef和define之间的区别。在SO上的这个问题中有很多好的帖子,但我不理解其中一个帖子所说的:
#define是一个预处理器标记:编译器本身不会看到它。
typedef是一个编译器标记:预处理器不关心它。
请问有人能够详细解释一下吗?我对这里的预处理器一词感到困惑。
6个回答

9

预处理器是在编译器之前运行的程序,主要执行文本替换。当你写:

#define X 10

int main() 
{
    int x = X;
}

预处理器将该文件作为输入,对其进行处理,并输出:
int main() 
{
    int x = 10;
}

然后编译器使用预处理的输出进行其操作。

typedef是编译器能够理解的结构。当你编写:

typedef unsigned int uint_32;

编译器知道uint32实际上是unsigned int的别名。这个别名由编译器本身处理,涉及比简单文本替换更多的逻辑。这在一个简单的例子中变得明显:
typedef int my_int;

int main()
{
    unsigned my_int x;  // oops, error
}

如果 typedef 是像预处理器一样的简单文本替换机制,那么它就可以工作,但是这是不允许的,编译会失败。

5

预处理器是在任何编译开始之前发生的阶段。它读取特定的宏和符号进行替换。通常只有一到两个步骤,扫描整个源文件并生成一个符号表来替换或扩展宏。

当所有替换完成后,语法分析器接管对源文件进行词法分析和语法分析,生成抽象语法树,生成代码,链接库并生成可执行文件/二进制文件。

在C/C++/ObjC中,预处理指令以'#'开头,后跟指令名称,例如"define"、"ifdef"、"ifndef"、"elif"、"if"、"endif"等。

预处理之前:

#define TEXT_MACRO(x) L##x
#define RETURN return(0)   
int main(int argc, char *argv[])
{
    static wchar_t buffer[] = TEXT_MACRO("HELLO WORLD");
    RETURN ;
}

预处理完成后:

int main(int argc, char *argv[])
{
    static wchar_t buffer[] = L"HELLO WORLD";
    return (0);
}

如果我记得没错,《C程序设计语言》第二版,作者是Kernighan和Ritchie,在其中有一个例子,他们展示了如何创建自己的预处理器并且它是如何工作的。此外,Plan9 C编译器将两个进程(编译和预处理)分开。让你能够在其中使用你自己的预处理器。
请查看一个有趣的多语言预处理器编写自己的预处理器。看到这些程序的输入和输出会让你更加深入地理解预处理器实际上是什么。
另一个小秘密:如果你有一个预处理器,你可以用拉丁文/德文/西班牙文编写C代码 :)

+1 正确答案,并且你回答得最快。不过附上一个示例会更好。 - Alex
一个例子会很好。 - Murphy316
@chris 没有注意到。谢谢 :) - Aniket Inge

4
C 和 C++ 预处理器是编译过程中非常早期的逻辑阶段。预处理器通过包含由 #include 指定的头文件的文本、通过有条件地消除文件中的部分内容(#if、#elif、#else、#endif 和变体 #ifdef 和 #ifndef),以及进行宏替换,将源文件转换为翻译单元。这些宏是通过 #define 定义的;预处理器扫描源代码可检测到这些宏。
预处理器会消除大多数以 # 开头的行(仅留下 #line 指令和其自己缩写版本上的 #line 指令,以告知编译器源代码的来源)。编译器会看到预处理后的结果并编译定义的源代码。
预处理器通常不会修改 typedef 关键字。一个疯狂的程序员可能会为 typedef 定义一个宏,而预处理器可能并不关心;只要存在与语言中任何关键字同名的宏,程序员就不能合法地包含任何系统头文件。除此之外,typedef 对于编译器而不是预处理器是一个问题。同样,sizeof() 也是预处理器不理解的内容。
通常有编译器选项允许您查看预处理输出。对于 gcc,选项为 -E 和 -P。

1
+1:@Jonathn Leffler,我来这里是为了理解man页面中的compiler proper是什么意思,而你已经回答了我的问题。 - heretoinfinity
关于“它只留下#line指令”的问题:这是C语言实现的一些内部行为。如果任何预处理器(独立的或内置于编译器中的)留下#line指令,那么按照C标准规定的预处理工作还没有完成,编译器(至少在概念上)仍然需要通过删除它们来完成预处理。在C标准指定的翻译模型中,#line指令不能留下,因为它们无法被词法语法解析。 - undefined

2
当涉及到编译时,您的源代码会经过许多步骤。 在这些步骤中,有预处理。
预处理器是在编译器之前运行的程序,并执行所有以“#”开头的指令,例如#include、#define等等。
在您的特定情况下,#define WORD newword是一条指令,它表示:“在尝试编译程序之前,将WORD的所有出现替换为newword”。
如果您想看到它的实际效果,请尝试运行cpp file.c并检查输出以了解其功能。
file.c
 #define WORD "newword"

 int main()
 {
     printf("this is word: %s\n", WORD);
     return 0;                           
 }

cpp运行后会变成什么

int main()
{
    printf("this is word: %s\n", "newword");
    return 0;
}

typedef被用来表示“如果我说到Type,就意味着我指的是struct more_complex_type”。它在编译时使用,并且在cpp处理前后保持不变。


2

预处理器是在编译器编译代码之前执行的“引擎”。#define #include 是预处理指令或宏,因此预处理器引擎执行与指令相关的代码。当预处理器完成后,编译器看不到指令/宏。但是像 typedef, if, while 等结构是由编译器理解的。


在Visual Studio中,当我们编写代码时,预处理器是否会在我们输入时运行(即在声明预处理器并尝试使用它之后)? - Murphy316
如果这样做有意义的话,那就值得去做。否则智能感知就毫无用处了。 - Lews Therin

1

这意味着预处理器在编译器之前运行,并在将源代码传递给编译器之前修改源代码。因此,编译器永远看不到某些代码。例如:

#define ONE 1
int x = ONE;

当您编译此代码时,预处理器会将其转换为以下内容:

int x = 1;

然后将新文本传递给编译器。因此,编译器看不到文本ONE


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接