常量C表达式是在编译时还是在运行时计算的?

常量C表达式是在编译时还是在运行时计算的?,c,optimization,compiler-construction,standards,c-preprocessor,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\u 100\

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

例如:

#define EXTERNAL_CLOCK_FREQUENCY    32768
#define TIMER_1_S                   EXTERNAL_CLOCK_FREQUENCY
#define TIMER_100_MS                TIMERB_1_S / 10
每次使用TIMER\u 100\u MS宏时,操作32768/10是否会在运行时发生

我希望避免以下情况:

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

总结
编译器需要能够计算常量积分表达式,因为它们是在编译时计算数组大小之类的内容所必需的。然而,标准只是说他们“可以”而不是“必须”这样做。因此,只有脑死编译器不会在编译时计算常量整数表达式,但对非常规编译器的程序集输出进行简单检查可以在编译时验证每种情况。

。这是一种语言标准(一直以来都是),独立于编译器

编辑

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

窗体的控制行

#define identifier token-sequence 
导致预处理器替换 标识符的后续实例 具有给定的令牌序列; 前后空白 讨论了罗肯序列

编辑结束

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

在代码中使用
TIMERB\u 100\u MS
的每个位置,预处理器都会将其替换为
32768/10


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

我不知道有任何标准保证它会得到优化。预处理器将用32768/10代替计时器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
...
#包括
#包括
#定义外部时钟频率32768
#定义定时器的外部时钟频率
#定义计时器\u 100 \u MS计时器\u 1 \u S/10
int main(int argc,字符**argv)
{
printf(“%d\n”,定时器100毫秒);
返回(0);
}
gcc-S测试
猫试验
...
popl%ebx
movl$3276,4%(esp)
所有LC0-“L000000000001$pb”(%ebx),%eax
移动%eax,(%esp)
调用L_printf$stub
...

宏只是文本替换,因此在您的示例中,在程序中编写
计时器\u 100_MS
是编写
32768/10
的一种奇特方式

因此,问题是编译器何时计算
32768/10
,这是一个常量积分表达式。我认为标准在这里不需要任何特定的行为(因为运行时和编译时的计算在效果上是不可区分的),但任何一个半途而废的编译器都会在编译时对其进行计算。

来自:

6.6常量表达式 语法 常量表达式:
条件表达式

描述 常量表达式可以是 在翻译过程中进行评估,而不是 而不是运行时,因此可以 可用于任何需要常数的地方 可能是

约束条件

不应使用常量表达式 包含赋值、增量、, 减量、函数调用或逗号 操作员,除非他们是 包含在子表达式中,该子表达式 未进行评估。96)

每个常量表达式应 求值为中的常数 its的epresentable值的范围 类型


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

但是,你不应该写:

#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))。这不是一个关键的区别,但你必须意识到这一点

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

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

  • 如果常量表达式在其表达式所具有的类型中不可表示,则它必须给出诊断消息
  • 这种表达式在转换时需要值的上下文中是允许的,例如,如果用作数组维度的大小
  • 如果编译器基本上可以在编译时计算结果,那么它应该使用该值,而不是在运行时重新计算。但也许还有一些理由这么做。我不知道

    <> > >编辑< /强>:很抱歉,我回答的问题好像是C++有关的。注意,今天您询问了C。表达式中的溢出在C中被视为未定义的行为,不管它是否发生在常量表达式中。当然,第二点在C中也是如此

    编辑:作为注释,如果将宏替换为类似于
    3*TIMER\u 100\u MS
    的表达式,则这将计算
    (3*32768)/10
    。因此,简单而直接的答案是“不,它不会每次都在运行时发生,因为由于优先级和关联性规则,除法可能根本不会发生”。我上面的回答假设宏总是被替换,这样除法就实际发生了。