更新,见下文!
我听说并读过C++ 0x允许编译器为以下代码段打印"Hello"
#include <iostream>
int main() {
while(1)
;
std::cout << "Hello" << std::endl;
}
Run Code Online (Sandbox Code Playgroud)
它显然与线程和优化功能有关.在我看来,这可能让许多人感到惊讶.
有人能够很好地解释为什么必须允许这样做吗?作为参考,最新的C++ 0x草案说明了6.5/5
在for语句的情况下,在for-init语句之外的循环,
- 不调用库I/O函数,和
- 不访问或修改易失性对象,以及
- 不执行同步操作(1.10)或原子操作(第29条)
可以通过实现来假设终止.[注意:这是为了允许编译器转换,例如删除空循环,即使无法证明终止也是如此. - 结束说明]
编辑:
这篇富有洞察力的文章谈到了标准文本
不幸的是,没有使用"未定义的行为".但是,只要标准说"编译器可以假设P",就暗示具有非-P属性的程序具有未定义的语义.
这是正确的,是否允许编译器为上述程序打印"Bye"?
这里有一个更有见地的线索,这是关于C的类似改变,由Guy完成上述链接文章开始.在其他有用的事实中,他们提出了一个似乎也适用于C++ 0x的解决方案(更新:这将不再适用于n3225 - 见下文!)
endless:
goto endless;
Run Code Online (Sandbox Code Playgroud)
看来,编译器不允许优化它,因为它不是循环,而是跳转.另一个人总结了C++ 0x和C201X的建议更改
通过编写一个循环,程序员断言或者环路不可见的东西的行为(执行I/O,访问volatile对象,或执行同步或原子操作), 或者,它最终会终止.如果我通过编写一个没有副作用的无限循环来违反这个假设,我对编译器撒谎,而我的程序的行为是未定义的.(如果我很幸运,编译器可能会警告我.)语言不提供(不再提供?)表达无可见行为的无限循环的方法.
2011年3月3日更新为n3225:委员会将案文移至1.10/24并说
实现可以假定任何线程最终将执行以下操作之一:
- 终止,
- 调用库I/O函数,
- 访问或修改易失性对象,或
- 执行同步操作或原子操作.
的goto把戏,不工作了!
正如标题所说,
一个典型的答案是:
允许任何和所有代码转换不会改变程序的可观察行为的规则
我们会不时地从某些实现中获取行为,这些行为归因于此规则.好多次错了.那么,这个规则究竟是什么呢.标准没有明确地将此规则作为一个部分或段落提及,那么究竟什么属于这条规则的范围?对我来说,这似乎是一个灰色区域,标准没有详细定义.有人可以根据标准的参考资料详细说明细节.
注意:将其标记为C和C++,因为它与两种语言都相关.
可以优化编译器删除无限循环,这不会改变任何数据,如
while(1)
/* noop */;
Run Code Online (Sandbox Code Playgroud)
从分析编译器可以推导出的数据流图,这样的循环是"死代码"而没有任何副作用.
是否删除了C90/C99标准禁止的无限循环?
C90或C99标准是否允许编译器删除此类循环?
更新:"Microsoft C版本6.0基本上做了这个优化.",请参阅caf的链接.
label: goto label;
return 0;
Run Code Online (Sandbox Code Playgroud)
将转变为
return 0;
Run Code Online (Sandbox Code Playgroud) c compiler-construction optimization standards infinite-loop
在本文中,以下是一段可以触发除零的代码示例:
if (arg2 == 0)
ereport(ERROR, (errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
/* No overflow is possible */
PG_RETURN_INT32((int32) arg1 / arg2);
Run Code Online (Sandbox Code Playgroud)
ereport这是一个宏,它扩展为对 - bool返回函数的调用,该函数errstart可能会或可能不会返回,并且?:对其返回值有条件(使用a ),调用另一个函数.在这种情况下,我认为ereport水平ERROR无条件地导致longjmp()其他地方.
因此,对上述代码的简单解释是,如果arg2非零,则将发生除法并返回结果,而如果arg2为零,则将报告错误并且不会发生除法.然而,链接的论文声称C编译器可以在零检查之前合法地提升除法,然后推断零检查从未被触发.他们唯一的推理似乎是不正确的
程序员没有通知编译器对ereport的调用(ERROR,:: :)没有返回.这意味着除法将始终执行.
John Regehr有一个更简单的例子:
void bar (void);
int a;
void foo3 (unsigned y, unsigned z)
{
bar();
a = y%z;
}
Run Code Online (Sandbox Code Playgroud)
根据这篇博文,clang在调用之上提升了模运算bar,并且他展示了一些汇编代码来证明它.
我对C的理解是适用于这些片段的
没有或不可以返回的函数在标准C中是格式良好的,并且这样的声明不需要特定的属性,钟声或哨声.
调用不返回或不返回的函数的语义是明确定义的,特别是通过C99中的6.5.2.2"函数调用".
由于ereport调用是一个完整的表达式,因此有一个序列点;.类似地,由于barJohn Regehr的代码中的调用是一个完整的表达式,因此在该处有一个序列点 …