33

如果我编写一个使用其他预处理器常量执行操作的#define,每次宏在运行时出现时都会计算最终值吗?这是否取决于编译器中的优化,或者它是否包含在标准中?

例子:

#define EXTERNAL_CLOCK_FREQUENCY    32768
#define TIMER_1_S                   EXTERNAL_CLOCK_FREQUENCY
#define TIMER_100_MS                TIMERB_1_S / 10

每次我使用 TIMER_100_MS 宏时,运行时会发生32768 / 10操作吗?

我想避免以下情况:

#define EXTERNAL_CLOCK_FREQUENCY    32768
#define TIMER_1_S                   EXTERNAL_CLOCK_FREQUENCY
#define TIMER_100_MS                3276

概括

编译器需要能够评估常量整数表达式,因为它们对于在编译时计算诸如数组大小之类的东西是必需的。然而,标准只说他们“可以”——而不是“必须”——这样做。因此,只有脑死亡的编译器不会在编译时评估常量整数表达式,但对非常规编译器的汇编输出进行简单检查将验证每种情况。

4

9 回答 9

35

宏只是文本替换,因此在您的示例TIMER_100_MS中,在程序中编写是一种奇特的编写方式32768 / 10

因此,问题是编译器何时评估32768 / 10,这是一个常量整数表达式。我认为该标准在这里不需要任何特定行为(因为运行时和编译时评估实际上无法区分),但是任何中途体面的编译器都会在编译时对其进行评估。

于 2009-01-12T17:57:55.900 回答
27

这里的大多数答案都集中在宏替换的影响上。但我想他想知道是否

32768 / 10

在编译时进行评估。首先,这是一个算术常量表达式,另外还有一个整数常量表达式(因为它只有整数类型的文字)。实现可以在运行时自由计算它,但它也必须能够在编译时计算它,因为

  1. 如果常量表达式不能以其表达式具有的类型表示,它必须给出诊断消息
  2. 此类表达式在需要转换时值的上下文中是允许的,例如,如果用作数组维度的大小。

如果编译器主要可以在编译时计算结果,它应该使用该值,而不是在运行时重新计算它我认为。但也许还有一些理由这样做。我不会知道的。

编辑:对不起,我回答的问题好像是关于 C++ 的。注意到今天您询问了 C。表达式中的溢出被视为 C 中的未定义行为,无论它是否发生在常量表达式中。当然,第二点在 C 中也是如此。

编辑:作为评论说明,如果宏被替换为表达式3 * TIMER_100_MS,那么这将评估(3 * 32768) / 10。因此,简单直接的答案是“不,它不会每次都在运行时发生,因为由于优先级和关联性规则,划分可能根本不会发生”。我上面的回答假设宏总是被替换,以便实际发生除法。

于 2009-01-12T21:07:33.397 回答
13

我不知道有任何标准可以保证它会被优化。预处理器将用 32768/10 替换 TIMER_100_MS,您可以通过运行 gcc -c 来查看。要查看编​​译器是否进一步优化,请运行 gcc -S 并检查汇编程序。使用 gcc 4.1,即使没有任何优化标志,它也会在编译期间减少为常量:

#include <stdlib.h>
#include <stdio.h>

#define EXTERNAL_CLOCK_FREQUENCY    32768
#define TIMER_1_S                   EXTERNAL_CLOCK_FREQUENCY
#define TIMER_100_MS                TIMER_1_S / 10

int main(int argc, char **argv)
{
  printf("%d\n", TIMER_100_MS);

  return(0);
}

gcc -S test.c
cat test.s

...
    popl    %ebx
    movl    $3276, 4(%esp)
    leal    LC0-"L00000000001$pb"(%ebx), %eax
    movl    %eax, (%esp)
    call    L_printf$stub
...
于 2009-01-12T17:57:09.443 回答
11

编译器应该优化那个表达式。我不认为这是标准所要求的,但我从未见过不会执行该任务的编译器。

但是,你不应该写:

#define TIMER_100_MS      TIMERB_1_S / 10

...因为这是一个等待发生的错误。您应该始终为涉及表达式的#defines 加上括号。

#define TIMER_100_MS      (TIMERB_1_S / 10)

考虑 :

i = 10 * TIMER_100_MS;

第一种情况将给出 32768 ((10 * TIMERB_1_S) / 10) ,第二种情况将给出 32760 (10 * (TIMERB_1_S / 10))。这里不是一个关键的区别,但你必须意识到它!

于 2009-01-12T19:30:35.807 回答
10

来自WG14/N1124 委员会草案 — 2005 年 5 月 6 日 ISO/IEC 9899:TC2

6.6 常量表达式

句法

常量表达式:
    条件表达式

描述

常量表达式可以在翻译期间而不是运行时进行评估,因此可以在常量可能存在的任何地方使用。

约束

常量表达式不应包含赋值、递增、递减、函数调用或逗号运算符,除非它们包含在未计算的子表达式中。96)

每个常量表达式都应计算为一个常量,该常量在其类型的可表示值范围内。

于 2009-01-12T19:30:12.440 回答
9

每次我使用TIMERB_100_MS宏时,运行时会发生 32768 / 10 操作吗?

代码中你使用的每个地方TIMERB_100_MS,它都会被预处理器替换32768 / 10

该表达式是否得到进一步优化(它的计算结果为常量)取决于您的编译器。

于 2009-01-12T17:56:30.303 回答
8

伙计们,这种转换称为“常量折叠”,甚至大多数学生编译器都会这样做。只要您有一个不是您或您的大学室友的人构建的编译器,并且您正在编译一种静态类型的语言,即使没有打开优化,您也可以依靠它。如果您正在处理一些允许更改/.

于 2009-01-13T06:23:05.197 回答
0

这是不正确的,编译器无法在编译时操作浮点数。如果您在编译时对 3276 值感到满意,那么您就可以开始了,但是编译器无法在编译时以浮点精度对此进行评估。浮点数对于编译器来说太难优化了,因为优化浮点数会导致数学表达式中出现意想不到的结果,所以一个像样的编译器(任何 gcc 版本、任何 clang 版本、任何 msvc 版本、任何 icc 版本)都不会将其简化为3276.8,故事结束。

而你问题的另一部分,你问是否会为每个宏扩展进行评估。同样,如果您对 3276 值没问题,那么答案是否定的,这取决于编译器、优化级别和背景,它可以放在常量表中,也可以内联在代码中。永远不会在运行时为每个宏扩展计算相同的表达式。同样,如果您希望浮点精度获得 3276.8,则该表达式将在运行时针对每个宏扩展进行评估。

在这里查看更多编译和优化方面: http ://www.agner.org/optimize/#manuals

于 2016-06-24T09:54:48.500 回答
-4

在编译时。这是一个语言标准(并且一直是)并且独立于编译器。

编辑

一位评论者要求提供参考 - 引用“C 编程语言”第 2 版附录 A12.3(第 229 页):

窗体的控制线

#define identifier token-sequence 

使预处理器用给定的标记序列替换标识符的后续实例;roken 序列周围的前导和尾随空格被丢弃

编辑结束

于 2009-01-12T17:55:44.797 回答