You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

回调注册时shared_ptr结构体副本指针异常致段错误求助

问题根源:栈内存失效引发的未定义行为

咱们直接点破核心问题:当你用void TestInit(TestStruct testSt)传值调用时,testStmyStruct栈上局部副本——它的生命周期只存在于TestInit函数执行的那段时间。当TestInit执行完毕返回后,这个栈上的testSt对象会被销毁:它的内存会被栈回收,内部的shared_ptr成员也会调用析构函数(不过因为全局的myStruct还持有对D对象的引用,所以原始的D实例不会被销毁,但栈上的shared_ptr本身的内存已经失效了)。

你注册回调时传递的是&testSt,也就是这个栈上局部对象的地址。当PeriodicActions在另一个线程触发回调时,这个地址指向的内存早已被栈重新分配给其他用途——这就是标准的悬空指针访问,属于C++里的未定义行为。

拆解你看到的诡异现象:

  • TestInittestSt.m1.get()和main里的值一致:这时候testSt还活着,栈内存没被破坏,shared_ptr的副本正确指向原始的D对象,所以指针值正常。
  • 回调里pStruct还是原来的栈地址,但m1的raw pointer变了:栈内存已经被后续的栈操作覆盖,原来shared_ptr结构里保存的指针值被改写了;而m2看起来没变纯粹是巧合——未定义行为的表现完全不可预测,这次可能m2的内存刚好没被覆盖,下次运行说不定m2也会出问题。
  • 用引用传递void TestInit(TestStruct &testSt)时正常:因为传递的是全局myStruct的引用,它的生命周期和程序一致,回调访问的是始终有效的内存,自然不会出问题。

解决办法

你可以根据场景选下面的方案:

  1. 继续使用引用传递:这是最简单的方案,确保回调持有的是全局myStruct的有效地址。
  2. 将对象移到堆上:如果必须传值,把TestStruct副本放到堆内存中,保证回调期间对象不会被销毁:
    void TestInit(TestStruct testSt) {
        // 把栈副本转移到堆上
        TestStruct* heapStruct = new TestStruct(std::move(testSt));
        std::cout << "TestInit: heapStruct: " << heapStruct << "\n";
        std::cout << "TestInit: heapStruct->m1.get(): " << heapStruct->m1.get() << "\n";
        std::cout << "TestInit: heapStruct->m2.get(): " << heapStruct->m2.get() << "\n";
        RegisterCallback(MyCallback, heapStruct);
        // 注意:一定要在合适的时机delete这个堆对象,避免内存泄漏
    }
    
  3. 直接使用全局对象的地址:既然你已经有全局的myStruct,可以直接在注册回调时传&myStruct,跳过TestInit的传值环节。

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

火山引擎 最新活动