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