C++ g++;循环的优化中断
几天前,我遇到了我认为是g++5.3中的一个bug,它涉及到在更高的C++ g++;循环的优化中断,c++,for-loop,optimization,nested-loops,C++,For Loop,Optimization,Nested Loops,几天前,我遇到了我认为是g++5.3中的一个bug,它涉及到在更高的-OX优化级别上嵌套for循环。(专门为-O2和-O3体验过)。问题是,如果有两个嵌套的for循环,它们有一些内部和来跟踪总迭代次数,一旦这个和超过其最大值,它就会阻止外部循环终止。我所能复制的最小代码集是: int main(){ int sum = 0; // Value of 100 million. (2047483648 less than int32 max.)
-OX
优化级别上嵌套for循环。(专门为-O2
和-O3
体验过)。问题是,如果有两个嵌套的for循环,它们有一些内部和来跟踪总迭代次数,一旦这个和超过其最大值,它就会阻止外部循环终止。我所能复制的最小代码集是:
int main(){
int sum = 0;
// Value of 100 million. (2047483648 less than int32 max.)
int maxInner = 100000000;
int maxOuter = 30;
// 100million * 30 = 3 billion. (Larger than int32 max)
for(int i = 0; i < maxOuter; ++i)
{
for(int j = 0; j < maxInner; ++j)
{
++sum;
}
std::cout<<"i = "<<i<<" sum = "<<sum<<std::endl;
}
}
但是,当使用g++-O2-o run.me main.cpp
编译时,外部循环无法终止。(这仅在maxInner*maxOuter>2^31
时发生)当总和不断溢出时,它不应以任何方式影响其他变量。我也在Ideone.com上测试了这个,测试用例如下:
因此,我的问题是双重的
std::cout
语句。注意:此问题与现有问题不同,例如:因为该问题的问题是sentinal变量溢出。但是,这个问题中的前哨变量
i
和j
都不会超过100m的值,更不用说2^31了。您的代码调用未定义的行为,因为整数和溢出。你说“这不应该以任何方式影响其他变量”。错。一旦你有了未定义的行为,所有的可能性都会消失。任何事情都有可能发生
gcc以假设不存在未定义行为的优化而闻名,如果出现未定义行为,我们可以说一些有趣的事情
解决方法:不要这样做 这是一个对正确代码完全有效的优化。你的代码不正确 GCC认为,达到循环退出条件
i>=maxOuter
的唯一方法是,在计算sum
的早期循环迭代中,如果您有符号整数溢出。编译器假定不存在有符号整数溢出,因为标准C中不允许有符号整数溢出。因此,i
可以优化为true
这由-faggressive循环优化
标志控制。通过向命令行参数添加-fno积极的循环优化
,您应该能够获得预期的行为。但最好是确保你的代码是有效的。使用无符号整数类型可获得有保证的有效环绕行为。
正如@hvd指出的,问题在于无效代码,而不是编译器
在程序执行期间,sum
值溢出int
范围。由于int
默认情况下是signed
的,并且signed
值的溢出在C中会导致未定义的行为*,因此编译器可以自由地执行任何操作。正如有人在某处指出的,龙可能会从你的鼻子里飞出来。结果只是没有定义
造成差异的原因是测试终端条件。当编译器优化您的循环时,它意识到它可以优化掉内部循环,使其
int sum = 0;
for(int i = 0; i < maxOuter; i++) {
sum += maxInner;
std::cout<<"i = "<<i<<" sum = "<<sum<<std::endl;
}
这看起来像是观察到的行为
*:根据第6.5节表达式:
如果在表达式求值期间出现异常情况(即,如果结果未在数学上定义或不在其类型的可表示值范围内),则行为未定义
**:根据附录H,H.2.2:
C的无符号整数类型在LIA中为“模”−1表示溢出或越界结果会自动换行
我对这个话题做了一些研究。我用
gcc
和g++
(Manjaro上的5.3.0版)编译了上面的代码,得到了一些非常有趣的东西
描述
为了使用gcc
(即C编译器)成功编译它,我已经替换了
#include <iostream>
...
std::cout<<"i = "<<i<<" sum = "<<sum<<std::endl;
main.cpp:
#ifdef ORIG
#include <iostream>
#else
#include <stdio.h>
#endif
int main(){
int sum = 0;
// Value of 100 million. (2047483648 less than int32 max.)
int maxInner = 100000000;
int maxOuter = 30;
// 100million * 30 = 3 billion. (Larger than int32 max)
for(int i = 0; i < maxOuter; ++i)
{
for(int j = 0; j < maxInner; ++j)
{
++sum;
}
#ifdef ORIG
std::cout<<"i = "<<i<<" sum = "<<sum<<std::endl;
#else
printf("i = %d sum = %d\n", i, sum);
#endif
}
}
#ifdef ORIG
#包括
#否则
#包括
#恩迪夫
int main(){
整数和=0;
//价值1亿(2047483648小于int32最大值)
int maxInner=100000000;
int maxOuter=30;
//1亿*30=30亿(大于int32最大值)
对于(int i=0;i std::coutWhen你写的时候,你的程序可以做任何事情。人们总是通过打开优化来发现“编译器错误”。我还没有看到他们的代码没有被破坏的情况。你很幸运,只得到了一个无限循环——龙可能会在我们说话的时候从你鼻子里出来。你不能“包含”未定义的行为。即使在时间上-如果你在代码中的某个点执行了一些非法操作,甚至在你接近该操作之前发生的一切都是未定义的。@Chris,这就是为什么它被称为未定义的行为。对我们人类来说,似乎合乎逻辑的行为对优化器来说并不重要,这是一个相当复杂的野兽。可能是d这也许是一篇有趣的评论,但它没有回答OP提出的两个明确的问题,也没有试图回答。鉴于OP出于某种原因决定接受你的帖子作为正确答案,它不太可能发生任何事情,但作为提醒,通常像这样的帖子会被删除。Putti不过,在一篇也能回答问题的帖子中,把你的测试作为额外的评论是完全合适的。@hvd谢谢你的通知。我意识到
int i = 0;
for(int sum = 0;/* nothing here evaluates to true */; sum += maxInner) {
i++;
std::cout<<"i = "<<i<<" sum = "<<sum<<std::endl;
}
#include <iostream>
...
std::cout<<"i = "<<i<<" sum = "<<sum<<std::endl;
#include <stdio.h>
...
printf("i = %d sum = %d\n", i, sum);
$ gcc -x c -Wall -Wextra -O2 -DORIG=1 -o gcc_opt_orig main.cpp
#ifdef ORIG
#include <iostream>
#else
#include <stdio.h>
#endif
int main(){
int sum = 0;
// Value of 100 million. (2047483648 less than int32 max.)
int maxInner = 100000000;
int maxOuter = 30;
// 100million * 30 = 3 billion. (Larger than int32 max)
for(int i = 0; i < maxOuter; ++i)
{
for(int j = 0; j < maxInner; ++j)
{
++sum;
}
#ifdef ORIG
std::cout<<"i = "<<i<<" sum = "<<sum<<std::endl;
#else
printf("i = %d sum = %d\n", i, sum);
#endif
}
}