28
#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++ 运气不好还是有什么不同的原因?

4

3 回答 3

5

首先,很好的问题。

编辑:

我第一次并没有真正正确地阅读您的代码。您的代码中有一个重要的外部调用。这在本说明e8 00 00 00 00 callq 2c <_Z3foov+0x2c>1中。它不会提前调用地址 - 相反,它的目标将在链接时被替换。这就是链接的工作方式——elf 文件会说“这样那样的指令将在链接时解析到这个目标”。汇编程序没有完整列出,所以我们不知道这个调用的地址。大概它delete _value在代码中,但是 libstdc++(带有删除等)默认情况下是动态链接的。您可以使用我的编译器标志来更改它,或者在 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();
}

有了这个; 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  

对免费的呼吁不会去任何地方。

如果有问题,请自行优化。模板使这很容易(假设 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();
}

如果是矢量或其他东西,则可以使代码静默失败std::array<P, 4>,并依靠您的默认构造方法。

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

于 2014-01-18T17:54:12.343 回答
0

这可能由于多种原因而发生。也许出于某种原因,gcc 没有内联和叮当声。调高 gcc 上的内联旋钮可能会有所帮助。或者可能还有其他事情发生,例如由于某种原因 gcc 无法解决的别名问题。不通过 gcc 追踪以发现细节是不可能知道的。

归根结底,这只是两个不同的编译器进行不同的代码转换。可以增强 gcc 以涵盖这种情况。

于 2014-01-06T19:09:00.320 回答
0

因为std::array. 但是,由于 g++ 和 clang 不使用相同的标准库(libstdc++ 用于 g++,libc++ 用于 clang)。

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

于 2014-01-03T20:54:15.723 回答