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
你可以看到结果是不同的。这是未定义的行为,每个实现都会按其认为合适的方式进行。
现在同一函数中的一些局部变量正在发生这种情况。但考虑这种情况:
它是一个指针,可以说是用 new 分配的。你已经调用了删除。什么保证该内存现在不被其他人使用?
当堆栈增长时,该值最终会被覆盖。考虑这段代码:
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
超出范围后碰巧使用该内存的任何对象的内存损坏。
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
你可以看到结果是不同的。这是未定义的行为,每个实现都会按其认为合适的方式进行。
现在同一函数中的一些局部变量正在发生这种情况。但考虑这种情况:
它是一个指针,可以说是用 new 分配的。你已经调用了删除。什么保证该内存现在不被其他人使用?
当堆栈增长时,该值最终会被覆盖。考虑这段代码:
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
超出范围后碰巧使用该内存的任何对象的内存损坏。