回调注册时shared_ptr结构体副本指针异常致段错误求助
问题根源:栈内存失效引发的未定义行为
咱们直接点破核心问题:当你用void TestInit(TestStruct testSt)传值调用时,testSt是myStruct的栈上局部副本——它的生命周期只存在于TestInit函数执行的那段时间。当TestInit执行完毕返回后,这个栈上的testSt对象会被销毁:它的内存会被栈回收,内部的shared_ptr成员也会调用析构函数(不过因为全局的myStruct还持有对D对象的引用,所以原始的D实例不会被销毁,但栈上的shared_ptr本身的内存已经失效了)。
你注册回调时传递的是&testSt,也就是这个栈上局部对象的地址。当PeriodicActions在另一个线程触发回调时,这个地址指向的内存早已被栈重新分配给其他用途——这就是标准的悬空指针访问,属于C++里的未定义行为。
拆解你看到的诡异现象:
TestInit里testSt.m1.get()和main里的值一致:这时候testSt还活着,栈内存没被破坏,shared_ptr的副本正确指向原始的D对象,所以指针值正常。- 回调里
pStruct还是原来的栈地址,但m1的raw pointer变了:栈内存已经被后续的栈操作覆盖,原来shared_ptr结构里保存的指针值被改写了;而m2看起来没变纯粹是巧合——未定义行为的表现完全不可预测,这次可能m2的内存刚好没被覆盖,下次运行说不定m2也会出问题。 - 用引用传递
void TestInit(TestStruct &testSt)时正常:因为传递的是全局myStruct的引用,它的生命周期和程序一致,回调访问的是始终有效的内存,自然不会出问题。
解决办法
你可以根据场景选下面的方案:
- 继续使用引用传递:这是最简单的方案,确保回调持有的是全局
myStruct的有效地址。 - 将对象移到堆上:如果必须传值,把
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这个堆对象,避免内存泄漏 } - 直接使用全局对象的地址:既然你已经有全局的
myStruct,可以直接在注册回调时传&myStruct,跳过TestInit的传值环节。
内容的提问来源于stack exchange,提问作者mysticalzero




