是在编译时还是在运行时评估常量C表达式?

时间:2009-01-12 17:46:09

标签: c optimization compiler-construction standards c-preprocessor

如果我编写一个使用其他预处理器常量执行操作的 #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

摘要

编译器需要能够计算常量积分表达式,因为它们是在编译时计算数组大小等内容所必需的。但是,标准只说他们“可以” - 而不是“必须” - 这样做。因此,只有脑死亡的编译器不会在编译时评估常量积分表达式,但是对非常规编译器的汇编输出的简单检查将验证每种情况。

9 个答案:

答案 0 :(得分:32)

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

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

答案 1 :(得分:26)

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

32768 / 10

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

  1. 如果常量表达式在其表达式具有
  2. 的类型中无法表示,则必须提供诊断消息
  3. 在翻译时需要值的上下文中允许使用此类表达式,例如,如果用作数组维度的大小。
  4. 如果编译器可以在编译时主要计算结果,那么它应该使用该值,而不是在运行时重新计算它。但也许有一些理由仍然这样做。我不知道。

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

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

答案 2 :(得分:12)

我不知道有任何标准可以保证它会被优化。预处理器将替换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
...

答案 3 :(得分: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))。这里不是一个关键的区别,但你必须意识到它!

答案 4 :(得分:8)

  

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

代码中使用TIMERB_100_MS的每个位置,预处理器都会将其替换为32768 / 10

表达式是否得到进一步优化(评估为常量)取决于编译器。

答案 5 :(得分:8)

来自WG14/N1124 Committee Draft — May 6, 2005 ISO/IEC 9899:TC2

  

6.6常量表达式

     

语法

     

常量表达式:
  条件表达式

     

描述

     

常量表达式可以   在翻译期间评估   比运行时,因此可能   用于任何恒定的地方   可能是。

     

约束

     

常量表达式不得   包含赋值,增量,   递减,函数调用或逗号   运营商,除非他们是   包含在子表达式中   没有评估.96)

     

每个常量表达式都应该   评估到的常数   其范围的可以表达的价值   类型。

答案 6 :(得分:8)

伙计们,这种转变被称为“不断折叠”,甚至大多数学生编译器都会这样做。只要你有一个由你或你的大学室友建立的编译器并且你正在编译一个静态类型的语言,即使没有打开优化,你也可以依靠它。如果你正在处理一些允许改变/含义的古怪动态语言,那就不同了。

答案 7 :(得分:0)

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

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

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

答案 8 :(得分:-4)

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

修改

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

  

表单

的控制行
#define identifier token-sequence 
     

导致预处理器替换   标识符的后续实例   给定的令牌序列;   领先和尾随的空白   roken序列被删除

编辑结束