简体   繁体   English

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

[英]Are constant C expressions evaluated at compile time or at runtime?

If I write a #define that performs an operation using other preprocessor constants, is the final value computed each time the macro appears at runtime? 如果我编写一个使用其他预处理器常量执行操作的#define ,那么每次宏在运行时出现时计算的最终值是多少? Does this depend on optimizations in the compiler, or is it covered under a standard? 这取决于编译器中的优化,还是属于标准?

Example: 例:

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

Will the operation 32768 / 10 occur at runtime every time I use the TIMER_100_MS macro? 每次都会十分之三万二千七百六十八在运行期间发生的操作我用的是TIMER_100_MS宏?

I would like to avoid the following: 我想避免以下情况:

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

Summary 摘要

A compiler is required to be able to evaluate constant integral expressions because they are necessary for calculating things like array sizes at compile time. 编译器需要能够计算常量积分表达式,因为它们是在编译时计算数组大小等内容所必需的。 However, the standard only says they "can" -- not "must" -- do so. 但是,标准只说他们“可以” - 而不是“必须” - 这样做。 Therefore, only a brain-dead compiler would not evaluate a constant integral expressions at compile time, but a simple check of the assembly output for an unconventional compiler would verify each case. 因此,只有脑死亡编译器不会在编译时评估常量积分表达式,但是对非常规编译器的汇编输出的简单检查将验证每种情况。

Macros are simply textual substitution, so in your example writing TIMER_100_MS in a program is a fancy way of writing 32768 / 10 . 宏是简单的文本替换,所以在你的例子写TIMER_100_MS的计划是写的一个奇特的方式32768 / 10

Therefore, the question is when the compiler would evaluate 32768 / 10 , which is a constant integral expression. 因此,问题是编译器何时评估32768 / 10 ,这是一个常量积分表达式。 I don't think the standard requires any particular behavior here (since run-time and compile-time evaluation is indistinguishable in effect), but any halfway decent compiler will evaluate it at compile time. 我不认为标准在这里需要任何特定的行为(因为运行时和编译时评估在效果上是无法区分的),但任何中途正常的编译器都会在编译时对其进行评估。

Most answers in here focused on the effect of the macro substitution. 这里的大多数答案都集中在宏观替代的影响上。 But i think he wanted to know whether 但我想他想知道是否

32768 / 10

is evaluated at compile time. 在编译时进行评估。 First of all, that is an arithmetic constant expression, and in addition a integral constant expression (because it has only got literals of integer type). 首先,这是一个算术常量表达式,另外还有一个整数常量表达式(因为它只有整数类型的文字)。 The implementation is free to calculate it at runtime, but it must also be able to calculate it at compile time, because 该实现可以在运行时自由计算,但它也必须能够在编译时计算它,因为

  1. it must give a diagnostic message if a constant expression is not representable in the type that its expression has 如果常量表达式在其表达式所具有的类型中无法表示,则它必须提供诊断消息
  2. such expressions are allowed in contexts that require the value at translation time, for example if used as the size of an array dimension. 在翻译时需要值的上下文中允许使用此类表达式,例如,如果用作数组维度的大小。

If the compiler can principally calculate the result already at compile time, it should use that value, and not recalculate it at runtime i think. 如果编译器可以在编译时主要计算结果,那么它应该使用该值,而不是在运行时重新计算它。 But maybe there is some reason still to do that. 但也许有一些理由仍然这样做。 I wouldn't know. 我不知道。

Edit : I'm sorry i've answered the question as if it were about C++. 编辑 :对不起,我已经回答了这个问题,好像它是关于C ++的。 Noticed today you asked about C. Overflowing in an expression is deemed as undefined behavior in C, regardless of whether it happens in a constant expression or not. 今天你注意到了C。表达式中的溢出被认为是C中的未定义行为,无论它是否发生在常量表达式中。 The second point is also true in C, of course. 当然,第二点在C中也是如此。

Edit : As a comment notes, if the macro is substituted into an expression like 3 * TIMER_100_MS , then this would evaluate (3 * 32768) / 10 . 编辑 :作为评论说明,如果宏被替换为像3 * TIMER_100_MS这样的表达式,那么这将评估(3 * 32768) / 10 Therefore, the simple and direct answer is "No, it would not occur at runtime every time, because the division may not occur at all because of precedence and associativity rules" . 因此,简单而直接的答案是“不,它不会在每次运行时发生,因为由于优先级和关联性规则,可能根本不会发生划分” My answer above assumes that the macro is always substituted such that the division actually happens. 我上面的回答假设宏总是被替换,以便实际发生划分。

I'm not aware of any standard that guarantees it will be optimized out. 我不知道有任何标准可以保证它会被优化。 The preprocessor will substitute 32768/10 for TIMER_100_MS, which you can see by running gcc -c. 预处理器将替换32768/10替换TIMER_100_MS,您可以通过运行gcc -c看到它。 To see whether the compiler is optimizing further, run gcc -S and check out the assembler. 要查看编​​译器是否进一步优化,请运行gcc -S并检查汇编程序。 With gcc 4.1, even without any optimization flags, this gets reduced to the constant during compilation: 使用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
...

The compiler should optimize that expression out. 编译器应优化该表达式。 I don't think it's requited to by the standard, but I've never seen a compiler that would NOT perform that task. 我不认为这是标准所要求的,但我从未见过不会执行该任务的编译器。

However, you should NOT write: 但是,你不应该写:

#define TIMER_100_MS      TIMERB_1_S / 10

... because that's a bug waiting to happen. ...因为这是一个等待发生的错误。 You should always parenthesize #defines involving expressions. 你应该总是将涉及表达式的#defines括起来。

#define TIMER_100_MS      (TIMERB_1_S / 10)

Consider : 考虑:

i = 10 * TIMER_100_MS;

The first case would give 32768 ((10 * TIMERB_1_S) / 10) , the second 32760 (10 * (TIMERB_1_S / 10)). 第一种情况是32768((10 * TIMERB_1_S)/ 10),第二种情况是32760(10 *(TIMERB_1_S / 10))。 Not a critical difference here, but you MUST be aware of it! 这里不是一个关键的区别,但你必须意识到它!

Will the operation 32768 / 10 occur at runtime every time I use the TIMERB_100_MS macro? 每次使用TIMERB_100_MS宏时,操作32768/10都会在运行时发生吗?

Every place in your code where you use TIMERB_100_MS , it will be replaced with 32768 / 10 by the preprocessor. 在代码中的每个地方,你使用TIMERB_100_MS ,它会被替换32768 / 10由预处理器。

Whether that expression gets further optimized (it evaluates to a constant) is up to your compiler. 该表达式是否得到进一步优化(它的计算结果为常量)取决于编译器。

From the WG14/N1124 Committee Draft — May 6, 2005 ISO/IEC 9899:TC2 : 来自WG14 / N1124委员会草案 - 2005年5月6日ISO / IEC 9899:TC2

6.6 Constant expressions 6.6常量表达式

Syntax 句法

constant-expression: 常量表达式:
conditional-expression 条件表达式

Description 描述

A constant expression can be evaluated during translation rather than runtime, and accordingly may be used in any place that a constant may be. 可以在转换期间而不是运行时期间评估常量表达式,并且因此可以在常量可以在任何地方使用。

Constraints 约束

Constant expressions shall not contain assignment, increment, decrement, function-call, or comma operators, except when they are contained within a subexpression that is not evaluated.96) 常量表达式不应包含赋值,递增,递减,函数调用或逗号运算符,除非它们包含在未评估的子表达式中.9)

Each constant expression shall evaluate to a constant that is in the range of epresentable values for its type. 每个常量表达式应计算为其类型的epresentable值范围内的常量。

Folks, this transformation is called "constant folding" and even most student compilers do it. 伙计们,这种转变被称为“不断折叠”,甚至大多数学生编制者也会这样做。 As long as you have a compiler built by somebody not you or your college roommate and you're compiling a statically typed language, you can count on it even without optimization turned on. 只要你有一个由你或你的大学室友建立的编译器并且你正在编译一个静态类型的语言,即使没有打开优化,你也可以依靠它。 It's a different matter if you're dealing with some wacky dynamic language that is allowed to change the meaning of / . 如果你正在处理一些允许改变/的含义的古怪动态语言,那就不一样了。

It`s incorrect, compilers can not manipulate floating point numbers at compile time. 这是不正确的,编译器无法在编译时操作浮点数。 If you are satisfied with 3276 value at compile time then you are good to go, but there is no way compiler going to evaluate this at compile time with floating point precision. 如果您在编译时对3276值感到满意,那么您可以继续使用,但编译器无法在编译时使用浮点精度对此进行评估。 Floating points are too tricky for compilers to optimize, because optimizing a floating point number can lead to unexpected results in mathematical expressions, so a decent compiler (any gcc version, any clang version, any msvc version, any icc version) wont simplify it to 3276.8 , end of story. 浮点数对于编译器来说太难以优化,因为优化浮点数会导致数学表达式出现意外结果,所以一个不错的编译器(任何gcc版本,任何clang版本,任何msvc版本,任何icc版本)都不会将其简化为3276.8,故事结束。

And the other part of your question, you asked will it is going to be evaluated for each macro expansion. 问题的另一部分,你问过它是否会针对每次宏扩展进行评估。 Again if you are fine with 3276 value then the answer is no, it depends from compiler, optimization level and the background, it can be placed in constant table or it can be inlined in code. 再次,如果你没有3276值,那么答案是否定的,它取决于编译器,优化级别和背景,它可以放在常量表中,也可以在代码中内联。 Ever way you wont compute the same expression in runtime for each macro expansion. 永远不会为每个宏扩展在运行时计算相同的表达式。 Again, if you want floating point precision to get 3276.8, than that expression will be evaluated for each macro expansion in run time. 同样,如果您希望浮点精度得到3276.8,那么将在运行时为每个宏扩展计算该表达式。

Take a look here for more compilation and optimization aspects: http://www.agner.org/optimize/#manuals 请查看更多编译和优化方面: http//www.agner.org/optimize/#manuals

At compile time. 在编译时。 This is a language standard (and always have been) and independent on the compiler. 这是一种语言标准(并且一直都是)并且独立于编译器。

Edit 编辑

A commenter asked for a reference - quoting from "The C programming language" 2nd edition Appendix A12.3 (p. 229): 一位评论者要求参考 - 引用“C编程语言”第2版附录A12.3(第229页):

A control line of the form 表格的控制线

 #define identifier token-sequence 

causes the preprocessor to replace subsequent instances of the identifier with the given sequence of tokens; 使预处理器用给定的令牌序列替换标识符的后续实例; leading and trailing whitespace around the roken sequence is discaded roken序列周围的前导和尾随空格被删除

End of edit 编辑结束

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM