You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

线程在对象销毁后仍可执行且未触发段错误的原因探究

为什么线程访问已销毁对象的成员函数却没崩溃?

这个现象其实是**未定义行为(Undefined Behavior)**的一种“幸运”表现——不是每次都能这么“正常”输出的,只是这次刚好没触发崩溃而已。咱们来一步步拆解背后的原因:

1. 先理清代码的执行时序

你代码里的执行顺序大概是这样的:

  • main函数创建shared_ptr<X>对象x,指向一个X实例。
  • 启动线程,lambda捕获x的引用,在线程里调用x->f():先输出f-1,然后sleep 5秒。
  • main线程里调用x->execute(),输出execute-1。
  • main线程调用x.reset(),此时shared_ptr的引用计数降到0,X对象被销毁,调用~X()输出DestrX。
  • 5秒后,子线程的sleep结束,输出f-2。
  • 子线程执行完毕,main线程调用join()后输出End。

2. 为什么没触发段错误?

核心原因在于:你在f()函数里没有访问X对象的任何成员变量。

成员函数的代码是存储在类的代码段里的,并不属于某个具体的对象。当你调用x->f()时,编译器会把它转换成类似X::f(this)的形式——这里的this指针指向当前X对象。但如果对象已经被销毁,this就变成了悬空指针:

  • 如果f()里没有使用this去访问对象的成员数据(比如没有读取/修改X的成员变量),那即使this悬空,函数依然能正常执行代码段里的逻辑(比如打印字符串、sleep),因为这些操作不需要访问对象的内存区域。
  • 但如果f()里有访问对象成员的代码(比如X里有int a;,然后f()里写cout << a << endl;),那此时访问悬空this指向的内存,大概率会触发段错误,或者输出随机值。

3. 关于lambda捕获的额外风险

你用[&]()捕获了x的引用,这其实是另一个隐藏的坑:如果x这个shared_ptr对象本身被销毁了(比如main函数提前结束),那lambda里的引用就会变成悬空引用,此时x->f()的调用会直接崩溃。不过在你的代码里,x是main函数的局部变量,main还在运行,所以x本身一直存在,只是它指向的X对象被销毁了,所以这次没触发这个问题。

正确的做法是什么?

要避免这种悬空访问的风险,应该让线程持有X对象的shared_ptr拷贝,也就是把lambda的捕获改成[x]()(值捕获):

thread_ = std::thread( [x]() { x->f(); });

这样shared_ptr的引用计数会增加1,即使main线程调用x.reset(),引用计数也不会降到0,X对象会等到子线程执行完毕、线程里的shared_ptr被销毁后才会被回收,从根本上避免悬空访问。

内容的提问来源于stack exchange,提问作者John Marek

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 18:44:12