C++ 是什么阻止了g++;从消除运行时未使用的临时std::array开始?

C++ 是什么阻止了g++;从消除运行时未使用的临时std::array开始?,c++,c++11,g++,clang,g++4.8,C++,C++11,G++,Clang,G++4.8,另一方面,clang删除除return语句之外的所有代码 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

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

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-std=c++11-O3:
0000000000000000 :1
0:c3 retq 1

g++运气不好,或者有什么原因造成这种差异?

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


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

这可能是由于多种原因造成的。也许由于某种原因,gcc没有内联和叮当声。打开gcc上的内联旋钮可能会有所帮助。或者,可能还有其他一些问题,比如gcc由于某种原因无法解决的别名问题。如果不通过gcc追踪来发现细节,就不可能知道


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

首先,这是一个很好的问题

编辑:

第一次我没有正确地阅读你的代码。代码中有一个重要的外部调用。这在本说明中
e8 00 00 callq 2c 1
。它不会提前调用地址,而是在链接时替换它的目标地址。这就是链接的工作原理——elf文件会说“某某指令将在链接时解析到此目标。”汇编程序尚未完全列出,因此我们不知道此调用的地址。可能代码中的值是
delete
,但是默认情况下libstdc++(带delet等)是动态链接的。您可以通过使用我的编译器标志来改变这一点,或者在gdb中列出(即链接后)

回到答案:

这是一种特殊情况,gcc程序员选择不进行优化。原因是,运算符new和delete被标记为“弱符号”,链接器将寻找替代方法,选择用户提供的方法,如果没有找到,则返回

是对这背后的基本原理的讨论。这些操作符被设计为全局可替换,弱链接是一种解决方案

静态链接不会改变这一点,因为glibc中的free和malloc仍然可以在linktime更改

静态链接或使用链接时间优化应该使用内置的删除,但是在这种情况下,如果您改为静态链接,仍然会错过机会。然而,在您最初的示例中,没有这样的机会

汇编此文件:

clang -std=c++11 -O3:

0000000000000000 <_Z3foov>:1
   0:       c3                      retq   1
免费电话哪儿也去不了

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

#包括
#包括
P类{
公众:
P():_值(nullptr){}
~P(){delete_value;}
私人:
char*_值;
};
void foo(){
if(std::tuple\u size::value!=4)
断言(假);
}
int main(){
foo();
}
如果
std::array
是向量或其他东西,则可以使代码以静默方式失败,然后使用默认的构造方法


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

这些是具有不同优化策略的不同编译器!?您可以尝试添加类似于
P():_值(nullptr)的内容吗{std::cout@dasblinkenlight-构造函数的副作用当然不应该被优化掉。由于构造函数和析构函数都是内联的,编译器可以看到原始代码中没有副作用,问题是为什么g++不能使用这些知识来消除临时数组。clang的常量传播更优越(无论如何,在这种情况下)。自从断言被消除后,常量传播在clang和g++上都有效,但之后删除数组时会消除死代码。使用clang的测试也使用libstdc++。OP在对现已删除的答案的评论中指出,在我的系统上,我得到了相同的结果。clang不需要使用libc++。什么是未内联的?输出不包含任何外部调用。@treap运算符delete未内联,如未解析的callq指令所示。如何获取代码列表?@David Greene输出来自“g++-std=c++11-O3-c t.cc&&objdump-d t.o”。运算符delete未内联,但如果我更改为“if(_值)delete _值”然后if语句将被内联。仍然没有帮助。我可能应该补充一点,检查_value==nullptr并不能解决这个问题,但这是另一回事。您是否检查了将4替换为1时会发生什么情况?@cmaster我修复了我的答案以解决这个问题(没有双关语)@treap区别在于,对于数组,您得到了一个循环(gcc无法展开)而使用对直接获得平面版本。@treap注意一般性。不容易猜测展开是否有利可图,有时则不然(否则编译器将继续并始终这样做)。因此,除非您找到一个编译器没有展开的特定位置,但您碰巧知道展开将实现大型简化(就像这里我们可以完全删除代码的地方),否则请避免过早优化。
clang -std=c++11 -O3:

0000000000000000 <_Z3foov>:1
   0:       c3                      retq   1
#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();
}
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  
#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();
}