为什么无法消除这种unique_ptr死存储?

时间:2018-11-16 02:02:09

标签: c++ gcc compiler-optimization abi

<div class="speech-bubble"><span id="x" ></span></div>

对于ClangGCC,上述代码段都会生成类似以下内容的代码:

#include <memory>
#include <vector>
using namespace std;

vector<unique_ptr<int>> e;

void f(unique_ptr<int> u) {
    e.emplace_back(move(u));
}

我想知道为什么编译器会在此函数中生成f(std::unique_ptr<int, std::default_delete<int> >): mov rsi, QWORD PTR e[rip+8] # rsi: vector.end_ptr cmp rsi, QWORD PTR e[rip+16] # [e + rip + 16]: vector.storage_end_ptr je .L52 # Slow path, need to reallocate mov rax, QWORD PTR [rdi] # rax: unique_ptr<int> u add rsi, 8 # end_ptr += 8 mov QWORD PTR [rdi], 0 # <==== Do we need to set the argument u to null here? mov QWORD PTR [rsi-8], rax # *(end_ptr - 8) = u mov QWORD PTR e[rip+8], rsi # update end_ptr ret .L52: # omitted ?是否有任何要求编译器这样做的约定?

此外,对于更简单的情况,例如this

mov QWORD PTR[rdi], 0

为什么编译器会生成:

void f(unique_ptr<int> u);

void h(int x) {
    auto p = make_unique<int>(x);
    f(move(p));
}

call operator delete(void*, unsigned long) 的末尾,假设在调用h()之后p始终是nullptr

1 个答案:

答案 0 :(得分:2)

在这两种情况下,答案都是:因为从您移动的对象仍然会被破坏。

如果您查看为调用而生成的代码

void f(unique_ptr<int> u);

您会注意到,调用者为参数u创建对象,然后按照调用约定的要求调用其析构函数。如果内联了对f()的调用,则编译器很可能能够对其进行优化。但是为f()生成的代码无法控制u的析构函数,因此,假设u的析构函数,必须将u的内部指针设置为零将在函数返回后运行。

在您的第二个示例中,我们遇到了相反的情况:

void h(int x) {
    auto p = make_unique<int>(x);
    f(move(p));
}

与名称所暗示的相反,std::move()实际上并未移动对象。它所做的所有操作都将转换为右值引用,如果该引用的接收者选择了该值,则该引用的接收者可以从所引用的对象移动。实际的移动仅发生在例如通过move构造函数从给定参数构造另一个对象时发生。由于编译器在f()的定义点上对h()内部发生的事情一无所知,因此不能假设f()将始终从给定对象中移出。例如,f()仅在某些情况下可以返回或移动,而在其他情况下则不能。因此,编译器必须假定函数可以返回而不离开对象,并且必须为析构函数发出delete。该功能还可以执行移动分配而不是移动构造,在这种情况下,仍然需要外部析构函数来释放以前由新对象分配的所有权持有的对象的所有权……