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

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);

为什么这个方案能解决问题?

  1. 避免悬空指针:Span的shared_ptr被存在全局容器中,只要C侧还持有句柄,Span对象的引用计数就不会降到0,不会被提前释放。
  2. 避免重复释放:所有对shared_ptr的操作都在C++侧完成,C侧只传递句柄,不会接触到原始指针,也就不会出现多个shared_ptr各自管理同一个原始指针的情况。
  3. 线程安全:用std::mutex保护容器的读写,多线程环境下也能安全使用。

如果你的场景是单线程的,也可以简化掉锁,但加锁是更稳妥的做法。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 06:46:55