这是由于未定义的行为,您在循环的最后一个迭代中越界访问了数组
mc
。一些编译器可能会围绕没有未定义的行为的假设进行积极的循环优化。逻辑类似于以下内容:
- 越界访问
mc
是未定义的行为
- 假设没有未定义的行为
- 因此,
di < 4
始终为真,否则mc [di]
将调用未定义的行为
使用打开优化并使用
-fno-aggressive-loop-optimizations
标志的gcc会导致无限循环行为消失(
查看实时演示)。而使用优化但不使用-fno-aggressive-loop-optimizations的
实时示例会出现您观察到的无限循环行为。
代码的
godbolt实时示例显示
di < 4
检查被删除并替换为无条件jmp。
jmp .L6
这与
GCC pre-4.8 Breaks Broken SPEC 2006 Benchmarks中所述的情况几乎相同。该文章的评论非常好,值得一读。它指出,clang使用
-fsanitize=undefined
捕获了文章中的案例,但我无法为此案例复现,但是gcc使用
-fsanitize=undefined
可以(
现场查看)。围绕优化器对未定义行为进行推断的最臭名昭着的错误可能是
Linux内核空指针检查删除。
尽管这是一种激进的优化方式,但重要的是要注意,正如C++标准所说,未定义行为是:
本国际标准不强制执行的行为
这意味着基本上任何事情都有可能发生,它还指出(我强调):
[...] 允许的未定义行为范围从完全忽略情况 带有不可预测的结果, 到在翻译或程序执行期间以环境的特性的记录方式行为(无论是否发出诊断消息),到终止翻译或执行(伴随着发出诊断消息)[...]
为了从gcc获得警告,我们需要将cout
移动到循环外,然后我们会看到以下警告 (现场查看):
warning: iteration 3u invokes undefined behavior [-Waggressive-loop-optimizations]
for(di=0; di<4;di++,delta=mc[di]){ }
^
这已经足以为原作者提供足够的信息来找出问题所在。类似这样的不一致性通常是我们在处理未定义行为时看到的行为类型。要更好地理解为什么这种警告在面对未定义行为时可能是不一致的,可以阅读为什么无法在基于未定义行为进行优化时发出警告?。
请注意,-fno-aggressive-loop-optimizations
在gcc 4.8 发布说明中有记录。
cout << di
中提供警告的原因可能是,对于复数的流插入运算符将di
的地址传递给某些“不透明”的代码(或者复数的流插入运算符本身就是不透明的 - 尽管这让我感到惊讶)。根据那个“不透明”代码所做的事情,程序的行为仍然可以被定义。我并不是说在这种情况下不可能提供一个警告而没有太多的误报(甚至没有任何误报)。只是这将是相当困难的。 - Paul Groke