C #在预处理器上定义-只是在计算中复制粘贴吗?
如果我有C #在预处理器上定义-只是在计算中复制粘贴吗?,c,c-preprocessor,gcc,C,C Preprocessor,Gcc,如果我有 #define NUM (30 * 60) 预处理器是将(30*60)复制粘贴到代码中,还是在代码中出现NUM的任何位置写入1800?预处理器会将代码中NUM的实例精确地替换为您指定的-(30*60)。编译器可能(几乎肯定会)稍后将其优化为1800,从而节省运行时计算。答案是(30*60),这可能是在编译时计算的 正如其他人所说,预处理器只是将定义的标记替换为其定义的文本。然后完全由编译器自己来注意可以在编译时执行的任何算术(例如30*60) 在您的示例中,源文件中所有符合条件的NU
#define NUM (30 * 60)
预处理器是将
(30*60)
复制粘贴到代码中,还是在代码中出现NUM
的任何位置写入1800?预处理器会将代码中NUM
的实例精确地替换为您指定的-(30*60)
。编译器可能(几乎肯定会)稍后将其优化为1800
,从而节省运行时计算。答案是(30*60)
,这可能是在编译时计算的
正如其他人所说,预处理器只是将定义的标记替换为其定义的文本。然后完全由编译器自己来注意可以在编译时执行的任何算术(例如30*60
)
在您的示例中,源文件中所有符合条件的NUM
实例都将被文本(30*60)
替换
这就是你直接问的问题的简单答案。但还有几个问题值得探讨
避免了常见的陷阱
预处理器执行的文本替换是文本。也就是说,预处理器几乎不理解C语言的任何语法。因此,结果可能并不意味着你所期望的。例如,如果你有
#define N 30 + 60
int a = N * 2;
预处理的文本将读取inta=30+60*2代码>不会使a
如N*2
所预期的那样为180。相反,由于优先级a
变为150
解决这一问题的方法是在展开的文本中始终使用足够的括号,您的写作(30*60)
就是这种最佳实践的一个例子。当您开始使用包含形式参数的宏时,您会发现,明智地使用括号对于避免意外非常重要
让我们看看cpp
但我想超越简单的答案,尝试向您展示如何亲自探索预处理器的行为
对于像您的问题一样简单的代码,知道这是一个简单的文本替换应该足以预测将发生什么。但是,当您开始使用预处理器更复杂的功能时(从带有参数的宏开始),偶尔您会想要调试预处理器的使用。要做到这一点,通常最简单的方法是运行预处理器,而不必编译和运行任何代码
(类似地,有时您会想知道编译器本身做了什么,因此,在不创建二进制文件和执行代码的情况下编译为汇编语言是很有用的。我们将在下一节中对此进行介绍。)
从历史上看,预处理器是一个独立的程序,由编译器驱动程序命令在源文件上运行,在编译器第一次通过之前运行。在现代编译器实现中,预处理器通常不是作为单独的可执行文件实现的,但由于历史原因,它仍然可以在不编译的情况下调用
预处理器的常用名称是cpp
。在非常常见的GCC编译器套件中,它也可以作为GCC-E
调用。在这两种情况下,cpp
将读取其命令行上命名的文件,或者如果没有命名文件,则读取stdin
,并将其输出写入stdout
。该输出通常会被#line
指令修饰,以便编译器可以责怪正确的源文件。您通常可以使用命令行选项将其关闭,对于GCC实现,该选项是-P
给定此源代码:
#define NUM (30 * 60)
int n = NUM * 42;
char *str = "NUM";
我们可以像这样通过预处理器将其输入,并立即看到输出:
C:\...>cpp -P q19987548.c
int n = (30 * 60) * 42;
char *str = "NUM";
C:\...>
可以看到,这个代码片段声明了一个名为\u n
的位置,并用常量75600填充它,该常量正好是30*60*42
。因此,在本例中,它显然是由编译器计算的
一般来说,当对目标使用常规优化时,您应该假设编译器知道它在做什么,而不必太担心这一级别的细节
走出深渊
尽管C预处理器不是一种完整的编程语言,但它可以用来实现一些相当惊人的技巧
因为它与C语言本身是如此地解耦,所以它可以用来处理其他语言的源代码。我见过它在nroff
和troff
中用于生成手册页和其他文档。任何与标记化规则兼容的源文本,以及接受其空格的注入和C注释的删除,都可以用它来处理
#define NUM (30 * 60)
将用(30*60)
完全替换NUM
这就是为什么这样的事情有时会导致可怕和愚蠢的不稳定行为的原因:
#define H 0.1f
#define H2 2.f*H
现在,在实际代码中,应该计算O(h^2)阶函数的导数f
:
如果在替换之前,预处理器将计算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;
}
预处理是根据令牌替换定义的。当找到令牌NUM
时,它将被5-令牌序列(
,30
,*
,60
,)替换。
。实际的规则要稍微复杂一些,因为您可能会在replacmement中出现NUM
(此处不再替换),但这是一个大问题
作为
#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
$ { 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);
$