【问题标题】:The -O2 optimization breaks C++ code on some machines-O2 优化会破坏某些机器上的 C++ 代码
【发布时间】:2012-11-19 17:29:50
【问题描述】:

我有 3 台不同的机器,配置如下:

  1. OpenSuSe 12.1,linux 内核 3.1.10,gcc 4.6.2
  2. Debian 6,linux 内核 2.6.32,gcc 4.4.5
  3. CentOS 5.6,linux内核2.6.18,gcc 4.1.2

他们所有的架构都是 x86_64。请注意,CentOS 的软件版本比 Debian 的旧,但 OpenSuSe 软件的版本比 Debian 的新。

我有以下示例代码:

#include <cstdio>
#include <cstdlib>

unsigned int cols=5;
unsigned int rows=6;

int main()
{
        //allocating...
        double **mat=new double*[rows];
        double *col=new double[rows];
        for(unsigned int i=0;i<rows;++i)
        {
                mat[i]=new double[cols];
        }

        //filling with something...
        for(unsigned int i=0;i<rows;++i)
        {
                for(unsigned int j=0;j<cols;++j)
                {
                        mat[i][j]=i+j;
                }
        }

        //testing...
        unsigned long long sum,add;
        for(unsigned int i=0;i<cols;++i)
        {
                sum=0;
                for(unsigned int j=0;j<rows;++j)
                {
                        col[j]=mat[j][i];
                        add=*((unsigned long long*) (&(col[j])));
                        sum+=add;
                }

                printf("%llu\n",sum);
        }

        return 0;
}

如果我在没有任何选项的情况下编译此代码:

g++ code.cpp

它在所有机器上以相同的方式运行。

但如果我用-O2 编译它,它在第一台和第三台机器上的行为方式相同,但在第二台机器(Debian)上-O2 优化会破坏它。

这是此代码在第一台机器上的输出,带有-O2

4619567317775286272
9238008735643729920
9250393634618998784
9259400833873739776
9266719183268216832

这是在第二台机器上,-O2

0
4619567317775286272
9238008735643729920
9250393634618998784
9259400833873739776

第二台机器的输出看起来像第一台的输出,下移了一行。

如果我将(&amp;(col[j])) 替换为(&amp;(mat[j][i])),代码开始正常工作。

这是编译器在第二台机器上的输出,带有-v-O2 选项:

Using built-in specs.
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Debian 4.4.5-8' --with-bugurl=file:///usr/share/doc/gcc-4.4/README.Bugs --enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.4 --enable-shared --enable-multiarch --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.4 --libdir=/usr/lib --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-objc-gc --with-arch-32=i586 --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 4.4.5 (Debian 4.4.5-8) 
COLLECT_GCC_OPTIONS='-v' '-O2' '-shared-libgcc' '-mtune=generic'
 /usr/lib/gcc/x86_64-linux-gnu/4.4.5/cc1plus -quiet -v -D_GNU_SOURCE dmtest.cpp -quiet -dumpbase dmtest.cpp -mtune=generic -auxbase dmtest -O2 -version -o /tmp/cc6v7CNY.s
ignoring nonexistent directory "/usr/local/include/x86_64-linux-gnu"
ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/4.4.5/../../../../x86_64-linux-gnu/include"
ignoring nonexistent directory "/usr/include/x86_64-linux-gnu"
#include "..." search starts here:
#include <...> search starts here:
 /usr/include/c++/4.4
 /usr/include/c++/4.4/x86_64-linux-gnu
 /usr/include/c++/4.4/backward
 /usr/local/include
 /usr/lib/gcc/x86_64-linux-gnu/4.4.5/include
 /usr/lib/gcc/x86_64-linux-gnu/4.4.5/include-fixed
 /usr/include
End of search list.
GNU C++ (Debian 4.4.5-8) version 4.4.5 (x86_64-linux-gnu)
        compiled by GNU C version 4.4.5, GMP version 4.3.2, MPFR version 3.0.0-p3.
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
Compiler executable checksum: 5a2e15051eaa06a84cf6320b754ba993
COLLECT_GCC_OPTIONS='-v' '-O2' '-shared-libgcc' '-mtune=generic'
 as -V -Qy -o /tmp/ccL37GHG.o /tmp/cc6v7CNY.s
GNU assembler version 2.20.1 (x86_64-linux-gnu) using BFD version (GNU Binutils for Debian) 2.20.1-system.20100303
COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/4.4.5/:/usr/lib/gcc/x86_64-linux-gnu/4.4.5/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/4.4.5/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/4.4.5/:/usr/lib/gcc/x86_64-linux-gnu/
LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/4.4.5/:/usr/lib/gcc/x86_64-linux-gnu/4.4.5/:/usr/lib/gcc/x86_64-linux-gnu/4.4.5/../../../../lib/:/lib/../lib/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/4.4.5/../../../:/lib/:/usr/lib/
COLLECT_GCC_OPTIONS='-v' '-O2' '-shared-libgcc' '-mtune=generic'
 /usr/lib/gcc/x86_64-linux-gnu/4.4.5/collect2 --build-id --eh-frame-hdr -m elf_x86_64 --hash-style=both -dynamic-linker /lib64/ld-linux-x86-64.so.2 /usr/lib/gcc/x86_64-linux-gnu/4.4.5/../../../../lib/crt1.o /usr/lib/gcc/x86_64-linux-gnu/4.4.5/../../../../lib/crti.o /usr/lib/gcc/x86_64-linux-gnu/4.4.5/crtbegin.o -L/usr/lib/gcc/x86_64-linux-gnu/4.4.5 -L/usr/lib/gcc/x86_64-linux-gnu/4.4.5 -L/usr/lib/gcc/x86_64-linux-gnu/4.4.5/../../../../lib -L/lib/../lib -L/usr/lib/../lib -L/usr/lib/gcc/x86_64-linux-gnu/4.4.5/../../.. /tmp/ccL37GHG.o -lstdc++ -lm -lgcc_s -lgcc -lc -lgcc_s -lgcc /usr/lib/gcc/x86_64-linux-gnu/4.4.5/crtend.o /usr/lib/gcc/x86_64-linux-gnu/4.4.5/../../../../lib/crtn.o

这种行为的原因是什么?我的代码不正确还是 gcc 4.4.5 中的错误?

【问题讨论】:

  • 您知道doubleunsigned long long 在这些机器/编译器组合中的大小是否相同吗?无论如何,一旦你开始以这种方式搞乱类型转换,你就是在与未定义的行为调情。
  • 是的,我知道,但无论如何感谢您的评论。我用std::cout &lt;&lt; sizeof(unsigned long long) &lt;&lt; " " &lt;&lt; sizeof(double) &lt;&lt; std::endl;测试过,所有机器上的答案都是8 8
  • 下一步:读取汇编输出或在gdb中单步执行...优化错误并非未知。
  • g++ -Wall -O 编译,然后改进你的代码直到没有给出警告。
  • 我尝试使用-Wall-Wstrict-aliasing 以及它们两者进行编译,但我没有收到任何警告。不过,使用-fno-strict-aliasing 可以解决问题。

标签: linux g++


【解决方案1】:

这是类型双关语,因此是未定义的行为。我很惊讶你没有收到关于它的警告。

当然,它通常按您期望的方式工作,但它仍然是未定义的行为。

要绕过未定义的行为部分,您可以强制转换为 char * 并连续读取每个字节。这是否解决了您所看到的行为?

【讨论】:

  • 是的,它修复了。两种变体:memcpy(&amp;add,&amp;col[j],8);std::copy((char*)&amp;col[j],((char*)&amp;col[j])+8,(char*)&amp;add); 按我的预期工作。
【解决方案2】:

根据this blog entry,GCC 4.4 对-O2 实施严格的别名规则。因此,类型双关语访问会导致未定义的行为。不幸的是,为了获得违反严格别名规则的警告,您必须使用-Wstrict-aliasing 进行编译。

也许较新版本的 GCC 已恢复到旧的行为,并将是否启用严格别名的决定留给用户。

【讨论】:

  • 感谢您的博客条目!我没有收到任何-W* 选项的警告,但解释很清楚。
猜你喜欢
  • 2016-04-22
  • 1970-01-01
  • 2016-12-12
  • 2011-04-13
  • 2012-10-08
  • 2018-04-14
  • 1970-01-01
  • 2020-06-23
  • 2015-12-25
相关资源
最近更新 更多