繁体   English   中英

g ++优化打破了循环

[英]g++ optimization breaks for loops

几天前,我遇到了我认为是g ++ 5.3中关于在更高的-OX优化级别嵌套for循环的-OX (专门针对-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++ -o run.me main.cpp编译它时,它按预期输出运行:

i = 0 sum = 100000000
i = 1 sum = 200000000
i = 2 sum = 300000000
i = 3 sum = 400000000
i = 4 sum = 500000000
i = 5 sum = 600000000
i = 6 sum = 700000000
i = 7 sum = 800000000
i = 8 sum = 900000000
i = 9 sum = 1000000000
i = 10 sum = 1100000000
i = 11 sum = 1200000000
i = 12 sum = 1300000000
i = 13 sum = 1400000000
i = 14 sum = 1500000000
i = 15 sum = 1600000000
i = 16 sum = 1700000000
i = 17 sum = 1800000000
i = 18 sum = 1900000000
i = 19 sum = 2000000000
i = 20 sum = 2100000000
i = 21 sum = -2094967296
i = 22 sum = -1994967296
i = 23 sum = -1894967296
i = 24 sum = -1794967296
i = 25 sum = -1694967296
i = 26 sum = -1594967296
i = 27 sum = -1494967296
i = 28 sum = -1394967296
i = 29 sum = -1294967296

但是,当使用g++ -O2 -o run.me main.cpp编译它时,外部循环无法终止。 (这仅在maxInner * maxOuter > 2^31 )虽然sum连续溢出,但它不应以任何方式影响其他变量。 我也在Ideone.com上测试了这个,测试用例如下所示: https ://ideone.com/5MI5Jb

因此我的问题是双重的。

  1. sum的值如何以某种方式影响系统? 没有任何决定是基于它的价值,它只是用于计数器和std::cout语句的目的。
  2. 什么可能导致不同优化级别的显着不同的结果?

非常感谢您花时间阅读并考虑我的问题。

注意:这个问题与现有问题不同,例如: 为什么x86上的整数溢出会导致GCC无限循环? 因为该问题的问题是sentinal变量的溢出。 然而,在这个问题ij两个sentinal变量都不会超过100m的值,更不用说2 ^ 31。

这是一种对正确代码完全有效的优化。 您的代码不正确。

GCC看到的唯一方法是,如果在计算sum早期循环迭代期间有符号整数溢出,则可以达到循环退出条件i >= maxOuter 编译器假定没有有符号整数溢出,因为标准C中不允许有符号整数溢出。因此, i < maxOuter可以优化为true

这由-faggressive-loop-optimizations标志控制。 通过在命令行参数中添加-fno-aggressive-loop-optimizations ,您应该能够获得所期望的行为。 但更好的是确保您的代码有效。 使用无符号整数类型来获得有保证的有效环绕行为。

您的代码调用未定义的行为,因为int sum溢出。 你说“这不应该以任何方式影响其他变量”。 错误。 一旦你有未定义的行为,所有的赔率都会被取消。 任何事情都可能发生。

gcc(in)以优化着称,假设没有未定义的行为,如果发生未定义的行为,就会说有趣的事情。

解决方案:不要这样做。

答案

正如@hvd指出的那样,问题在于无效代码,而不在编译器中。

在程序执行期间, sum值溢出int范围。 由于int是默认signed并且有signed值的溢出导致C中的未定义行为*,因此编译器可以自由地执行任何操作。 有人在某处注意到,龙可能会从你的鼻子里飞出来。 结果是未定义的。

差异-O2原因在于测试结束条件。 当编译器优化你的循环时,它意识到它可以优化掉内循环,从而实现它

int sum = 0;
for(int i = 0; i < maxOuter; i++) {
    sum += maxInner;
    std::cout<<"i = "<<i<<" sum = "<<sum<<std::endl;
}

它可能会更进一步,将其转化为

int i = 0;
for(int sum = 0; sum < (maxInner * maxOuter); sum += maxInner) {
    i++;
    std::cout<<"i = "<<i<<" sum = "<<sum<<std::endl;
}

说实话,我真的不知道它了什么,重点是,它可以做到这一点。 或者其他任何东西,记住龙,你的程序会导致未定义的行为。

突然,您的sum变量用于循环结束条件。 请注意,对于已定义的行为,这些优化完全有效。 如果你的sumunsigned (以及你的maxInnermaxOuter ),那么在maxOuter循环之后将达到(maxInner * maxOuter)值(也将是unsigned ),因为unsigned运算被定义**以按预期溢出。

现在,由于我们处于signed域中,编译器可以自由地假设, sum < (maxInner * maxOuter) ,因为后者溢出,因此未定义。 所以优化编译器最终会得到类似的东西

int i = 0;
for(int sum = 0;/* nothing here evaluates to true */; sum += maxInner) {
    i++;
    std::cout<<"i = "<<i<<" sum = "<<sum<<std::endl;
}

看起来像观察到的行为。

* :根据C11标准草案 ,第6.5节表达式:

如果在计算表达式期间发生异常情况(即,如果结果未在数学上定义或未在其类型的可表示值范围内),则行为未定义。

** :根据C11标准草案 ,附件H,H.2.2:

在LIA-1意义上,C的无符号整数类型是'modulo',因为溢出或越界结果会静默地换行。


我做了一些关于这个主题的研究。 我用gccg++ (Manjaro上的5.3.0版本)编译了上面的代码,并得到了一些非常有趣的东西。

描述

为了用gcc (C编译器)成功编译它,我已经替换了

#include <iostream>
...
std::cout<<"i = "<<i<<" sum = "<<sum<<std::endl;

#include <stdio.h>
...
printf("i = %d sum = %d\n", i, sum);

#ifndef ORIG包装这个替换,所以我可以有两个版本。 然后我运行了8个编译:{ gccg++ } x { -O2"" } x { -DORIG=1"" }。 这产生以下结果:

结果

  1. gcc-O2-DORIG=1 :无法编译,缺少<iostream> 不足为奇

  2. gcc-O2"" :生成编译器警告并“正常”运行。 程序集中的外观显示内部循环已优化( j增加100000000),外部循环变量与硬编码值-1294967296进行比较。 因此,GCC 可以检测到这一点并在程序正常运行时做一些聪明的事情。 更重要的是,发出警告以警告用户未定义的行为。

  3. gcc"" , - -DORIG=1 :无法编译,缺少<iostream> 不奇怪。

  4. gcc"""" :在没有警告的情况下编译。 没有优化,程序按预期运行。

  5. g++-O2-DORIG=1 :编译没有警告,在无限循环中运行。 这是OP的原始代码运行。 C ++程序集对我来说很难理解。 虽然增加了100000000。

  6. g++-O2""编译警告。 只需更改输出的打印方式就可以更改编译器警告的发出。 运行“正常”。 通过组装,AFAIK内环得到优化。 至少再次与-1294967296进行比较并增加100000000。

  7. g++"" , - -DORIG=1 :编译时没有警告。 没有优化,“正常”运行。

  8. g++"""" :dtto

对我来说最有趣的部分是找出改变印刷时的差异。 实际上,从所有组合中,只有OP使用的组合产生无限循环程序,其他组合无法编译,不优化或优化警告并保持理智。

按照示例构建命令和我的完整代码

$ gcc -x c -Wall -Wextra -O2 -DORIG=1 -o gcc_opt_orig  main.cpp

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
    }
}

暂无
暂无

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

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