优化掉一个“while(1);"在 C++0x
已更新,见下文!
我听说并读到 C++0x 允许编译器为以下代码段打印Hello"
I have heard and read that C++0x allows an compiler to print "Hello" for the following snippet
#include <iostream>
int main() {
while(1)
;
std::cout << "Hello" << std::endl;
}
它显然与线程和优化能力有关.不过在我看来,这会让很多人感到惊讶.
It apparently has something to do with threads and optimization capabilities. It looks to me that this can surprise many people though.
有人对为什么有必要允许这样做有很好的解释吗?作为参考,最新的 C++0x 草案在 6.5/5
Does someone have a good explanation of why this was necessary to allow? For reference, the most recent C++0x draft says at 6.5/5
一个循环,在 for 语句的情况下,在 for-init-statement 之外,
A loop that, outside of the for-init-statement in the case of a for statement,
- 不调用库 I/O 函数,并且
- 不访问或修改易失性对象,并且
- 不执行同步操作 (1.10) 或原子操作(第 29 条)
可能由实现假设终止.[注意:这是为了允许编译器转换即使无法证明终止,也可以删除空循环.― 尾注 ]
may be assumed by the implementation to terminate. [ Note: This is intended to allow compiler transfor- mations, such as removal of empty loops, even when termination cannot be proven. ― end note ]
这篇富有洞察力的文章介绍了该标准文本
不幸的是,没有使用未定义行为"这个词.然而,每当标准说编译器可以假设 P"时,就暗示具有非 P 属性的程序具有未定义的语义.
Unfortunately, the words "undefined behavior" are not used. However, anytime the standard says "the compiler may assume P," it is implied that a program which has the property not-P has undefined semantics.
是否正确,是否允许编译器为上述程序打印Bye"?
Is that correct, and is the compiler allowed to print "Bye" for the above program?
这里有一个更有见地的线程,这是对 C 的类似更改,由 Guy 开始,完成了上面链接的文章.在其他有用的事实中,他们提出了一个似乎也适用于 C++0x 的解决方案(更新:这将不再适用于 n3225 - 见下文!)
There is an even more insightful thread here, which is about an analogous change to C, started off by the Guy done the above linked article. Among other useful facts, they present a solution that seems to also apply to C++0x (Update: This won't work anymore with n3225 - see below!)
endless:
goto endless;
似乎不允许编译器将其优化掉,因为它不是循环,而是跳转.另一个人总结了 C++0x 和 C201X 中提议的更改
A compiler is not allowed to optimize that away, it seems, because it's not a loop, but a jump. Another guy summarizes the proposed change in C++0x and C201X
通过编写一个循环,程序员断言要么循环执行一些可见行为(执行 I/O,访问volatile 对象,或执行同步或原子操作),或它最终终止.如果我违反了那个假设通过编写一个没有副作用的无限循环,我在撒谎编译器,我的程序的行为是未定义的.(如果我幸运的话,编译器可能会警告我.)该语言不提供(不再提供?)一种表达无限循环的方法,而无需可见的行为.
By writing a loop, the programmer is asserting either that the loop does something with visible behavior (performs I/O, accesses volatile objects, or performs synchronization or atomic operations), or that it eventually terminates. If I violate that assumption by writing an infinite loop with no side effects, I am lying to the compiler, and my program's behavior is undefined. (If I'm lucky, the compiler might warn me about it.) The language doesn't provide (no longer provides?) a way to express an infinite loop without visible behavior.
<小时>
2011 年 1 月 3 日更新 n3225:委员会将文本移至 1.10/24 并说
Update on 3.1.2011 with n3225: Committee moved the text to 1.10/24 and say
该实现可能假设任何线程最终都会执行以下操作之一:
The implementation may assume that any thread will eventually do one of the following:
- 终止,
- 调用库 I/O 函数,
- 访问或修改易失性对象,或
- 执行同步操作或原子操作.
goto
技巧将不再工作!
推荐答案
有人对为什么有必要允许这样做有很好的解释吗?
Does someone have a good explanation of why this was necessary to allow?
是的,Hans Boehm 在N1528:为什么无限循环的未定义行为?,虽然这是 WG14 文档,但基本原理也适用于 C++,该文档同时引用了 WG14 和 WG21:
Yes, Hans Boehm provides a rationale for this in N1528: Why undefined behavior for infinite loops?, although this is WG14 document the rationale applies to C++ as well and the document refers to both WG14 and WG21:
正如 N1509 正确指出的那样,目前的草案基本上给出了6.8.5p6 中无限循环的未定义行为.一个主要问题这样做是为了允许代码在一个潜在的非终止循环.例如,假设我们有以下循环,其中 count 和 count2 是全局变量(或有它们的地址取),而p是局部变量,地址未被取:
As N1509 correctly points out, the current draft essentially gives undefined behavior to infinite loops in 6.8.5p6. A major issue for doing so is that it allows code to move across a potentially non-terminating loop. For example, assume we have the following loops, where count and count2 are global variables (or have had their address taken), and p is a local variable, whose address has not been taken:
for (p = q; p != 0; p = p -> next) {
++count;
}
for (p = q; p != 0; p = p -> next) {
++count2;
}
可以将这两个循环合并并替换为以下循环吗?
Could these two loops be merged and replaced by the following loop?
for (p = q; p != 0; p = p -> next) {
++count;
++count2;
}
没有 6.8.5p6 中对无限循环的特殊规定,这将被禁止:如果第一个循环没有终止,因为 q指向一个循环列表,原来从不写入count2.因此它可以与另一个访问或访问的线程并行运行更新计数2.转换后的版本不再安全尽管存在无限循环,但它确实访问了 count2 .就这样转换可能会引入数据竞争.
Without the special dispensation in 6.8.5p6 for infinite loops, this would be disallowed: If the first loop doesn't terminate because q points to a circular list, the original never writes to count2. Thus it could be run in parallel with another thread that accesses or updates count2. This is no longer safe with the transformed version which does access count2 in spite of the infinite loop. Thus the transformation potentially introduces a data race.
在这种情况下,编译器不太可能能够证明循环终止;它必须明白 q 点到一个非循环列表,我认为这超出了大多数人的能力主流编译器,如果没有整个程序,通常是不可能的信息.
In cases like this, it is very unlikely that a compiler would be able to prove loop termination; it would have to understand that q points to an acyclic list, which I believe is beyond the ability of most mainstream compilers, and often impossible without whole program information.
非终止循环施加的限制是对编译器无法执行的终止循环的优化证明终止,以及实际的优化非终止循环.前者比后者更常见后者,通常更有趣的是优化.
The restrictions imposed by non-terminating loops are a restriction on the optimization of terminating loops for which the compiler cannot prove termination, as well as on the optimization of actually non-terminating loops. The former are much more common than the latter, and often more interesting to optimize.
显然也有带有整数循环变量的 for 循环编译器很难证明终止,并且因此编译器很难重构循环没有 6.8.5p6.甚至像
There are clearly also for-loops with an integer loop variable in which it would be difficult for a compiler to prove termination, and it would thus be difficult for the compiler to restructure loops without 6.8.5p6. Even something like
for (i = 1; i != 15; i += 2)
或
for (i = 1; i <= 10; i += j)
处理起来似乎很重要.(在前一种情况下,一些基本数字需要理论来证明终止,在后一种情况下,我们需要了解有关 j 的可能值的一些信息.环绕式对于无符号整数可能会使某些推理进一步复杂化.)
seems nontrivial to handle. (In the former case, some basic number theory is required to prove termination, in the latter case, we need to know something about the possible values of j to do so. Wrap-around for unsigned integers may complicate some of this reasoning further.)
这个问题似乎适用于几乎所有的循环重组转换,包括编译器并行化和缓存优化转换,这两者都有可能获得的重要性,并且对于数字代码已经很重要了.这似乎可能会转化为巨大的成本能够以最自然的方式编写无限循环,尤其是因为我们大多数人很少故意编写无限循环.
This issue seems to apply to almost all loop restructuring transformations, including compiler parallelization and cache-optimization transformations, both of which are likely to gain in importance, and are already often important for numerical code. This appears likely to turn into a substantial cost for the benefit of being able to write infinite loops in the most natural way possible, especially since most of us rarely write intentionally infinite loops.
与 C 的一个主要区别是 C11 为控制常量表达式的表达式提供了一个例外,这与 C++ 不同并使您的具体示例在 C11 中得到明确定义.
The one major difference with C is that C11 provides an exception for controlling expressions that are constant expressions which differs from C++ and makes your specific example well-defined in C11.
相关文章