std::function的对象什么时候销毁?

When the object of std::function is destroyed?

std::function的对象什么时候销毁? 为什么当变量fn1超出范围时指向std::function的对象的指针仍然有效(你看代码片段很好,http://cpp.sh/6nnd4)?

// function example
#include <iostream>     // std::cout
#include <functional>   // std::function, std::negate

// a function:
int half(int x) {return x/2;}

int main () {
    std::function<int(int)> *pFun;
    
    {
        std::function<int(int)> fn1 = half;                    // function
     
         pFun= &fn1;   
         std::cout << "fn1(60): " << (*pFun)(60) << '\n';
    }

    std::cout << "fn1(60): " << (*pFun)(90) << '\n';

  return 0;
}

Why the pointer to the object of std::function is still valid when the variable fn1 is out of the scope?

让我举一个更简单的例子,使用整数。但是如果你胆子大,可以尝试阅读std::function版本的汇编程序。

int main () {
    int a = 0;
    int *c = nullptr;
    {
        int b = 1;
        c = &b;
    }
    a = *c;
    return a;
}

这是用 gcc 10.2 -O0 生成的,但其他编译器的输出非常相似。我将对其进行评论以帮助理解。

main:
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], 0    # space for `a`
        mov     QWORD PTR [rbp-16], 0   # space for `c`
        mov     DWORD PTR [rbp-20], 1   # space for `b`
        lea     rax, [rbp-20]           # int *tmp = &b  Not really, but enough for us
        mov     QWORD PTR [rbp-16], rax # c = tmp
        mov     rax, QWORD PTR [rbp-16] 
        mov     eax, DWORD PTR [rax]    # int tmp2 = *tmp
        mov     DWORD PTR [rbp-4], eax  # a = tmp2
        mov     eax, DWORD PTR [rbp-4]  
        pop     rbp                         
        ret                             # return a

和程序 return 1,正如您看到汇编程序时所预期的那样。你可以看到,b 没有“失效”,因为我们没有回滚堆栈,也没有改变它的值。如果它有效,这将与您所处的情况类似。

现在让我们启用优化。这里是用最低的-O1编译的

这里是用gcc编译的:

main:
        mov     eax, 0
        ret

这是用 clang 编译的:

main:                                   # @main
        mov     eax, 1
        ret

这是 visual studio:

main    PROC                                            ; COMDAT
        mov     eax, 1
        ret     0
main    ENDP

你可以看到结果是不同的。这是未定义的行为,每个实现都会按其认为合适的方式进行。

现在同一函数中的一些局部变量正在发生这种情况。但考虑这种情况:

  1. 它是一个指针,可以说是用 new 分配的。你已经调用了删除。什么保证该内存现在不被其他人使用?

  2. 当堆栈增长时,该值最终会被覆盖。考虑这段代码:

int* foo() {
    int a = 0;
    return &a;
}

void bar() {
    int b = 1;
}


int main () {
    int *ptr = foo();
    bar();
    int a = *ptr;
    return a;
}

它不是 return 1 或 0。它 return 是 139。

这里是 a good read on this

简短的回答是 C++ 不验证指针的内容。那是开发商的责任。它仅验证变量 pFun 在范围内。

在 C++ 中,开发人员通常有责任确保他们的指针指向有效对象。在这种情况下,fn1 可能已在堆栈上创建。由于许多编译器以这种方式实现局部变量。当 fn1 超出范围时,编译器将不再允许使用变量 fn1。但是,未定义堆栈支持 fn1 上的内存会发生什么。在您的情况下,编译器保持不变,因此 (*pFun)(90) 恰好仍然有效。但是,运行 相同的代码在另一个编译器上可能不会。事实上,简单地打开编译器优化可能会导致它停止工作。这完全取决于编译器是重新使用该内存,还是将其从堆栈中释放。

在您的示例中,作用域是一个代码块。如果范围改为名为 MyFunction2 的单独函数,那么当 MyFunction2 退出时,与 fn1 关联的堆栈内存将被释放出堆栈,并且内存可用于重用。通过中断和在 MyFunction2 之后调用的任何代码。因此数据更有可能被更改,导致 (*pFun)(90) 出错。

现在调试像这样的崩溃是相当困难的。想象一下,如果您改为写入 pFun,这将导致在 fn1 超出范围后碰巧使用该内存的任何对象的内存损坏。