为什么无法消除这种unique_ptr死存储? [英] Why this dead store of unique_ptr cannot be eliminated?

查看:107
本文介绍了为什么无法消除这种unique_ptr死存储?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

vector<unique_ptr<int>> e;

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

对于两个 C语 GCC ,上面的代码段生成类似这样的东西:

For both Clang and GCC, the above code snippet generates something like:

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

我想知道为什么编译器生成 mov QWORD PTR [rdi],0 在此函数中?还有任何约定要求编译器这样做吗?

I was wondering why does the compiler generate mov QWORD PTR[rdi], 0 in this function? Is there any convention that requires compiler to do so?

此外,对于

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()的末尾,因为在调用<$ c之后,p始终为 nullptr $ c> f ?

at the end of h(), given that p is always nullptr after the invocation of f?

推荐答案

在这两种情况下,答案都是:因为对象

In both of these cases, the answer is: because the object you moved from will still be destroyed.

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

If you look at the code generated for a call to

void f(unique_ptr<int> u);

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

you will notice that the caller creates the object for parameter u and calls its destructor afterwards as mandated by the calling convention. In case the call to f() is inlined, the compiler will most likely be able to optimize this away. But the code generated for f() has no control over the destructor of u and, thus, has to set the internal pointer of u to zero assuming that the destructor of u will run after the function returns.

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

In your second example, we have sort of the inverse situation:

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

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

Contrary to what the name may suggest, std::move() does not actually move an object. All it does is cast to an rvalue reference which allows the recipient of that reference to move from the object referred to—if he so choses. The actual move only happens, e.g., when another object is constructed from the given argument via a move constructor. Since the compiler does not know anything about what happens inside f() at the point of definition of h(), it can't assume that f() will always move from the given object. For example, f() could simply return or move only in some cases and not in others. Therefore, the compiler has to assume that the function might return without moving from the object and has to emit the delete for the destructor. The function could also perform a move assignment instead of a move construction, in which case the outer destructor would still be needed to release ownership of the object previously held by whatever was assigned ownership of the new object…

这篇关于为什么无法消除这种unique_ptr死存储?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆