预处理器中的#define - 它只是在计算中复制粘贴吗?

时间:2013-11-14 20:19:05

标签: c c-preprocessor

如果我有

#define NUM (30 * 60)

预处理器会将代码粘贴(30 * 60)到代码,还是在代码中出现NUM的任何地方写入1800?

5 个答案:

答案 0 :(得分:2)

答案是( 30 * 60 ),它(可能)在编译时计算

正如其他人所说,预处理器只是将定义的标记替换为其定义的文本。然后完全由编译器本身来注意可以在编译时执行的任何算术(例如30*60)。

在您的示例中,源文件中NUM的所有符合条件的实例都将被文本( 30 * 60 )替换。

这就是你提出的直接问题的简单答案。但还有一些值得探索的问题。

避免了常见的陷阱

预处理器执行的文本替换是文字的。也就是说,预处理器几乎不了解C语言的任何语法。所以结果可能并不意味着你的期望。例如,如果你有

#define N 30 + 60
int a = N * 2;

预处理的文字会显示int a = 30 + 60 * 2;a不会使N*2a的预期相同。相反,由于优先级( 30 * 60 )变为150。

解决方案是在扩展文本中始终使用足够的括号,而您的写作cpp就是这种最佳实践的一个示例。当您开始使用包含形式参数的宏时,您会发现明智地使用括号对于避免意外情况非常重要。

我们来看看cpp

但我想超越简单的答案,并试着向您展示如何自己探索预处理器的行为。

对于像你的问题一样简单的代码,知道它是一个简单的文本替换应该足以预测会发生什么。但是,当您开始使用预处理器的更复杂功能(从带参数的宏开始)时,您偶尔会想要调试预处理器的使用情况。要做到这一点,通常最简单的方法是在不编译和运行任何代码的情况下运行预处理器。

(类似地,有时你会想知道编译器本身做了什么,为此,编译成汇编语言而不创建二进制文件并执行代码是很有用的。我们将在下面的内容中查看。下一节。)

预处理器在历史上是一个单独的程序,它由源文件上的编译器驱动程序命令运行,并且在编译器正确的第一次传递之前运行。在现代编译器实现中,预处理器通常不是作为单独的可执行文件实现的,但由于历史原因,它仍然可以在没有编译的情况下调用。

预处理器的通常名称是gcc -E。在极其常见的GCC编译器套件中,它也可以作为cpp调用。在这两种情况下,stdin将读取在其命令行上命名的文件,或者如果没有命名文件则读取stdout,并将其输出写入#line。该输出通常将使用-P指令进行修饰,以便编译器可以归咎于正确的源文件。您通常可以使用命令行选项将其关闭,对于GCC实现,该选项为#define NUM (30 * 60) int n = NUM * 42; char *str = "NUM";

鉴于此源代码:

(30 * 60) * 42

我们可以像这样通过预处理器提供它并立即看到输出:

C:\...>cpp -P q19987548.c
int n = (30 * 60) * 42;
char *str = "NUM";

C:\...>

另请注意,替换不会发生在字符串文字中。一些较旧的编译器确实替换了字符串中的文本,但是因为采用了第一个C标准,所以没有。

但是-S计算的时间是什么时候?

更进一步,我们可以要求编译器向我们展示其生成的汇编代码,并使用它来发现它是否在编译时计算表达式。在许多C编译器中,这是使用 .file "q19987548.c" .globl _n .data .align 4 _n: .long 75600 .globl _str .section .rdata,"dr" LC0: .ascii "NUM\0" .data .align 4 _str: .long LC0 选项完成的,就像在GCC中一样。使用GCC以Windows上的x86为典型示例,我们可以编译上面的代码片段,并获得以下汇编输出:

_n

可以看出,这段代码片段声明了一个名为30 * 60 * 42的位置,并用常量75600填充它,正好是nroff。因此,在这种情况下,编译器清楚地计算出它。

通常,当对目标使用通常的优化时,您应该假设编译器知道它在做什么,而不是过多担心这个细节级别。

走出深端

C预处理器可用于一些相当惊人的技巧,尽管它不是一种完整的编程语言。

由于它与C语言本身脱钩,因此可用于处理其他语言的源代码。我已经看到它用于在troff和{{1}}中生成手册页和其他文档。任何与其标记化规则兼容并接受其空白注入和删除C注释的源文本都可以使用它进行处理。

答案 1 :(得分:1)

预处理器会使用您指定的内容NUM替换代码中(30 * 60)的实例。编译器可能(几乎肯定会)稍后将其优化为1800,从而节省运行时计算。

答案 2 :(得分:1)

#define NUM (30 * 60)

NUM完全替换为(30 * 60)

这就是为什么这样的事情有时会导致一个可怕的&愚蠢的不稳定行为:

#define H  0.1f
#define H2 2.f*H

现在在实际代码中应该计算O(h ^ 2)的函数f的导数:

float num_dev = (f(x+H)-f(x-H))/H2

如果预处理器在替换之前计算2.f*H的结果,那么一切都会按顺序排列。

但是,因为预处理器只是将H2替换为2.f*H,这会产生错误的结果。

float num_dev = ((f(x+H)-f(x-H))/2.f)*H

(我添加了新的()以明确我的观点,编译器不会这样做。)

因此,在表达式周围添加括号始终是一个非常好的主意,就像你一样。

 #define H2 (2.f*H)

这是一个实际的例子:

#include <stdio.h>

#define H 0.1f
#define H2 2.f*H

int main(void) {

    float a = (4.f-2.f)/H2;

    float b = (4.f-2.f)/(H2);

    printf("%f   %f\n", a, b);

    return 0;

}

输出:

0.100000   10.000000

答案 3 :(得分:1)

预处理是根据令牌替换定义的。当找到令牌NUM时,它将替换为5令牌序列(30*60)。实际的规则稍微复杂一些,因为你可能会在替换中出现NUM(它不会再被替换),但这就是全局。

对于乘法,大多数(如果不是全部)编译器将为您进行乘法运算并在生成的代码中使用1800。

答案 4 :(得分:0)

假设您正在使用,可以使用-E标志来显示预处理器输出。您可以使用以下单行测试您自己的编译器的行为:

$ { echo "#define NUM (30 * 60)"; echo "int a = NUM;" ; } | gcc -E -
# 1 "<stdin>"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "<stdin>"

int a = (30 * 60);
$