C/C++混合编程中如何从raw pointer安全获取shared_ptr?
解决C/C++混合编程中跨语言传递Span指针的段错误问题
最近在做C/C++混合编程时遇到了段错误问题,场景是跨语言调用跟踪Span的创建和销毁接口,运行时直接报Segmentation fault (core dumped)。先贴一下我原来的代码:
common.h
#ifdef __cplusplus typedef opentracing::Span CTraceSpan; #else struct CTraceSpan; typedef struct CTraceSpan CTraceSpan; #endif extern "C" CTraceSpan* c_StartServerSpan(); extern "C" CTraceSpan* c_FinishServerSpan(CTraceSpan *span);
trace2c.cpp
extern "C" CTraceSpan* c_StartServerSpan(){ auto server_span = hwtrace::StartServerSpan(); return server_span.get(); } extern "C" CTraceSpan* c_FinishServerSpan(CTraceSpan *span){ std::shared_ptr<opentracing::Span> s_span(std::move(span)); auto server_span = hwtrace::FinishServerSpan(s_span); return server_span.get(); }
trace.c
CTraceSpan *span = NULL; span = c_StartClientSpan(); c_FinishClientSpan(span);
查了资料才发现问题出在智能指针的所有权管理上,直接用原始指针创建std::shared_ptr是绝对禁止的——就像相关技术讨论中提到的:
绝不能像这样从普通指针创建shared_ptr:
shared_ptr<ParticleEmitter>(&e)这会导致ParticleEmitter被释放两次:一次是当存储它的vector超出作用域时,另一次是当shared_ptr超出作用域时。
而且我原来的代码还有个更严重的问题:c_StartServerSpan里的server_span是局部的shared_ptr,返回原始指针后,函数结束时这个shared_ptr就销毁了,Span对象的引用计数直接降到0,直接被释放了!C侧拿到的是个悬空指针,后续调用c_FinishServerSpan操作已经被释放的内存,不崩溃才怪。
那怎么解决呢?核心思路就是在C++侧统一管理智能指针的生命周期,C侧只传递不透明的句柄,不直接持有原始指针,下面是具体的修改方案:
推荐方案:用句柄映射管理Span生命周期
我们在C侧维护一个线程安全的容器,用来存储std::shared_ptr<opentracing::Span>,给C侧返回一个唯一的句柄(比如自增ID),C侧只需要传递这个句柄,C侧根据句柄找到对应的shared_ptr进行操作。
修改后的common.h
#ifdef __cplusplus typedef opentracing::Span CTraceSpan; #else // C侧不需要知道Span的具体结构,用不透明句柄代替指针 typedef uintptr_t CTraceSpanHandle; #endif extern "C" CTraceSpanHandle c_StartServerSpan(); extern "C" void c_FinishServerSpan(CTraceSpanHandle handle);
修改后的trace2c.cpp
#include <unordered_map> #include <mutex> #include <cstdint> // 全局容器存储Span的shared_ptr,加锁保证多线程安全 std::unordered_map<uintptr_t, std::shared_ptr<opentracing::Span>> span_storage; std::mutex span_mutex; // 自增句柄,保证每个Span有唯一标识 uintptr_t next_handle = 1; extern "C" CTraceSpanHandle c_StartServerSpan(){ auto server_span = hwtrace::StartServerSpan(); std::lock_guard<std::mutex> lock(span_mutex); uintptr_t current_handle = next_handle++; // 将shared_ptr存入容器,延长生命周期 span_storage[current_handle] = server_span; return current_handle; } extern "C" void c_FinishServerSpan(CTraceSpanHandle handle){ std::lock_guard<std::mutex> lock(span_mutex); auto it = span_storage.find(handle); if (it != span_storage.end()){ // 调用Finish接口,传入容器中存储的shared_ptr hwtrace::FinishServerSpan(it->second); // 从容器移除,释放shared_ptr,自动管理Span的生命周期 span_storage.erase(it); } }
修改后的trace.c
CTraceSpanHandle span_handle = 0; span_handle = c_StartServerSpan(); // ... 执行你的业务逻辑 ... c_FinishServerSpan(span_handle);
为什么这个方案能解决问题?
- 避免悬空指针:Span的
shared_ptr被存在全局容器中,只要C侧还持有句柄,Span对象的引用计数就不会降到0,不会被提前释放。 - 避免重复释放:所有对
shared_ptr的操作都在C++侧完成,C侧只传递句柄,不会接触到原始指针,也就不会出现多个shared_ptr各自管理同一个原始指针的情况。 - 线程安全:用
std::mutex保护容器的读写,多线程环境下也能安全使用。
如果你的场景是单线程的,也可以简化掉锁,但加锁是更稳妥的做法。
内容的提问来源于stack exchange,提问作者ansyser
相关产品推荐
相关产品推荐

