什么阻止g ++消除运行时未使用的临时std :: array?

时间:2014-01-01 15:50:23

标签: c++ c++11 g++ clang g++4.8

#include <array>
#include <cassert>

class P {
  public:
    P() : _value(nullptr) {}
    ~P() { delete _value; }

  private:
   char *_value;
};

void foo() {
  if(std::array<P, 4>().size() != 4)
    assert(false);
}

函数foo()创建一个临时数组来检查程序员所期望的大小。使用-O1或更高的g ++数字时,assert不会失败,并且会从生成的代码中删除对__assert_fail的调用。但是g ++仍然会生成代码来首先构造然后破坏现在未使用的数组。

g++ -std=c++11 -O3 [4.8.2]:

0000000000000000 <_Z3foov>:1
   0:       55                      push   %rbp1
   1:       66 0f ef c0             pxor   %xmm0,%xmm01
   5:       53                      push   %rbx1
   6:       48 83 ec 28             sub    $0x28,%rsp1
   a:       66 0f 7f 04 24          movdqa %xmm0,(%rsp)1
   f:       48 8d 5c 24 20          lea    0x20(%rsp),%rbx1
  14:       48 89 e5                mov    %rsp,%rbp1
  17:       66 0f 7f 44 24 10       movdqa %xmm0,0x10(%rsp)1
  1d:       0f 1f 00                nopl   (%rax)1
  20:       48 83 eb 08             sub    $0x8,%rbx1
  24:       48 8b 3b                mov    (%rbx),%rdi1
  27:       e8 00 00 00 00          callq  2c <_Z3foov+0x2c>1
  2c:       48 39 eb                cmp    %rbp,%rbx1
  2f:       75 ef                   jne    20 <_Z3foov+0x20>1
  31:       48 83 c4 28             add    $0x28,%rsp1
  35:       5b                      pop    %rbx1
  36:       5d                      pop    %rbp1
  37:       c3                      retq   1
另一方面,

clang删除除return语句之外的所有代码。

clang -std=c++11 -O3:

0000000000000000 <_Z3foov>:1
   0:       c3                      retq   1

g ++运气不好还是有差异的原因?

3 个答案:

答案 0 :(得分:5)

首先,很好的问题。

编辑:

我第一次没有正确阅读你的代码。 您的代码中有一个重要的外部呼叫。这是在e8 00 00 00 00 callq 2c <_Z3foov+0x2c>1指令中。它不会提前调用地址 - 而是在链接时替换它的目标。这就是链接的工作原理 - 精灵文件会说&#34;这样的指令会在链接时解析为这个目标。&#34;汇编程序尚未完全列出,因此我们不知道此调用的地址。据推测它在代码中是delete _value,但libstdc ++(带delet等)默认情况下是动态链接。您可以使用我的编译器标志来更改它,或者在gdb中进行列表(即在链接之后)。

回到答案:

这是一个特殊情况,gcc程序员已经做出了不优化的选择。运营商new和delete被标记为“弱符号”的原因是&#39;并且链接器将查找备选方案,选择提供的用户或如果没有找到则退回。

Here讨论了这背后的基本原理。这些运营商旨在全球可更换,弱连接是一种解决方案。

静态链接并没有改变这一点,因为glibc中的free和malloc仍然可以在链接时更改。

静态链接或使用链接时优化 应使用内置删除但是在这种情况下,如果您改为静态链接,则仍有机会丢失。然而,在你的原始例子中,没有这样的机会。

编译:

#include <array>
#include <cassert>
#include <cstdlib>

void * operator new(std::size_t n) throw(std::bad_alloc)
{
  return malloc(n);
}
void operator delete(void * p) throw()
{
if(p != nullptr)
  free(p);
}

class P {
  public:
    P() : _value(nullptr) {}
    ~P() { delete _value; }

  private:
   char *_value;
};

void foo() {
  if(std::array<P, 4>().size() != 4)
    assert(false);
}

int main(){
foo();
}

这个;      g ++ -std = c ++ 11 -O3 -static -Wa,-alh test.cpp -o test

链接到libstdc ++ / glibc 静态,因此它应该知道free和malloc是什么,但是它并没有完全意识到foo是微不足道的。

但是,nm -gC test | grep free会生成弱符号__free_hook,我找到here的解释。因此,在使用gcc进行编译时,free和malloc(以及操作符new和delete)的行为在链接时实际上总是可以更改。这就是阻止优化的原因 - 令人烦恼的是-fno-weak将那些符号留在那里。

以上段落属实,但是遗漏了优化的结果,而不是避免优化的原因。

通过链接时间优化,可以将gcc哄骗进行此优化,但首先必须使用-flto构建其他所有内容,包括libstdc ++。

以下是静态构建示例时gcc为我做的最多:

Dump of assembler code for function _Z3foov:
0x08048ef0 <+0>:    push   %esi
0x08048ef1 <+1>:    push   %ebx
0x08048ef2 <+2>:    sub    $0x24,%esp
0x08048ef5 <+5>:    movl   $0x0,0x10(%esp)
0x08048efd <+13>:   lea    0x20(%esp),%ebx
0x08048f01 <+17>:   movl   $0x0,0x14(%esp)
0x08048f09 <+25>:   lea    0x10(%esp),%esi
0x08048f0d <+29>:   movl   $0x0,0x18(%esp)
0x08048f15 <+37>:   movl   $0x0,0x1c(%esp)
0x08048f1d <+45>:   lea    0x0(%esi),%esi
0x08048f20 <+48>:   sub    $0x4,%ebx
0x08048f23 <+51>:   mov    (%ebx),%eax
0x08048f25 <+53>:   test   %eax,%eax
0x08048f27 <+55>:   je     0x8048f31 <_Z3foov+65>
0x08048f29 <+57>:   mov    %eax,(%esp)
0x08048f2c <+60>:   call   0x804dea0 <free>
0x08048f31 <+65>:   cmp    %esi,%ebx
0x08048f33 <+67>:   jne    0x8048f20 <_Z3foov+48>
0x08048f35 <+69>:   add    $0x24,%esp
0x08048f38 <+72>:   pop    %ebx
0x08048f39 <+73>:   pop    %esi
0x08048f3a <+74>:   ret  

免费电话不会去任何地方。

如果是问题,请自行优化。模板使这很容易(假设std :: array作为模板参数传递,否则为什么要检查它的大小()?)。

#include <array>
#include <cassert>

class P {
  public:
    P() : _value(nullptr) {}
    ~P() { delete _value; }

  private:
   char *_value;
};

void foo() {
  if(std::tuple_size<std::array<P, 4> >::value != 4)
    assert(false);
}

int main(){
foo();
}

如果std::array<P, 4>是向量或其他内容,则可以使代码无声地失败,并使用默认的构造方法。

当我添加nm -C test时,

W operator new(unsigned int, void*)输出#include <new>,因此至少可以更改新位置链接时间(它是一个弱符号) - 其他是特殊的,以及它们的最终目标驻留在libstdc ++中(同样,默认情况下它们是动态链接的)。

答案 1 :(得分:0)

因为std::array的构造函数中可能存在副作用。但是,因为g ++和clang不使用相同的标准库(用于g ++的libstdc ++和用于clang的libc ++)。

对于为什么clang删除代码的问题,也许在libc ++中内联std::array的构造函数,因此优化器可以看到没有副作用,因此需要保留代码。

答案 2 :(得分:0)

这可能由于多种原因而发生。也许gcc不会出于某种原因内联和铿锵。打开gcc上的内联旋钮可能会有所帮助。或者也许还有其他事情正在发生像gcc因某些原因无法解决的别名问题。如果不通过gcc跟踪来发现细节,就不可能知道。

最重要的是,它只是两个不同的编译器进行不同的代码转换。可以增强gcc以涵盖这种情况。