#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 ++运气不好还是有差异的原因?
答案 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以涵盖这种情况。