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

tre*_*eap 28 c++ g++ clang c++11 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);
}
Run Code Online (Sandbox Code Playgroud)

该函数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
Run Code Online (Sandbox Code Playgroud)

另一方面,clang删除除return语句之外的所有代码.

clang -std=c++11 -O3:

0000000000000000 <_Z3foov>:1
   0:       c3                      retq   1
Run Code Online (Sandbox Code Playgroud)

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

use*_*280 5

首先,很好的问题.

编辑:

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

回到答案:

这是一个特殊情况,gcc程序员已经做出了不优化的选择.原因是运算符new和delete被标记为"弱符号",链接器将寻找备选方案,选择提供的用户或如果没有找到则退回.

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

静态链接不会改变这一点,因为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();
}
Run Code Online (Sandbox Code Playgroud)

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

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

然而,nm -gC test | grep free产生弱符号 __free_hook,我在这里找到了解释.因此,在使用gcc进行编译时,free和malloc(以及operator 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  
Run Code Online (Sandbox Code Playgroud)

对免费的呼吁不会随处可见.

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

#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();
}
Run Code Online (Sandbox Code Playgroud)

如果std::array<P, 4>是向量或其他东西,代码可以使其无声地失败,并回退到您的默认构造方法.

nm -C testW operator new(unsigned int, void*)当我添加时输出#include <new>,所以至少可以更改放置链接时间(它是一个弱符号) - 其他是特殊的,它们的最终目标位于libstdc ++中(同样,默认情况下它们是动态链接的).

  • @treap的不同之处在于,对于一​​个数组,你会得到一个循环(gcc无法展开),而对于你可以直接获得平面版本的对. (2认同)

小智 0

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

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