博客
关于我
C++调用Go方法的字符串传递问题及解决方案
阅读量:426 次
发布时间:2019-03-06

本文共 2560 字,大约阅读时间需要 8 分钟。

C++调用Go方法的字符串传递问题及解决方案

在一个技术项目中,我们需要在C++代码中调用Go方法,传递字符串参数。经过一系列的验证和分析,我们发现了一个棘手的问题,并最终找到了解决方案。以下是详细的分析过程和解决方法。


现象

在实际项目中,我们需要将需要拉起的ServiceModule信息传递给Go的Loader。在C++代码中,通过调用Go方法传递字符串参数时,发现以下问题:

  • 在Go协程中取到的字符串值与预期不一致。
  • 经过一段时间的分析和验证,我们发现问题的根源。

  • 背景知识

  • Go的内存管理机制

    Go语言自带内存回收机制(GC),通过make等函数申请的内存不需要手动释放。这意味着在C++代码中直接传递内存指针可能带来安全隐患。

  • C++字符串对象的内存布局

    在C++中,为std::string变量赋值新字符串后,.c_str().size()的结果会联动变化。尤其是,当字符串内容发生变化时,.c_str()指向的地址也会发生变化。

  • Go与C++之间的内存交互

    使用-buildmode=c-shared生成的共享库文件中定义了C++中Go变量的类型映射关系。例如,GoString实际是一个结构体,包含一个字符指针和一个长度成员。


  • 原理及解释

    通过代码示例解释具体现象及原因:

    C++侧代码

    // Created by w00526151 on 2020/11/5#include 
    #include
    #include "libgoloader.h"GoString buildGoString(const char* p, size_t n) { // 结构体定义:{ const char *p; ptrdiff_t n; } return {p, static_cast
    (n)};}int main() { std::cout << "test send string to go in C++"; // 1. 创建GoString对象 const char* tmpStr = "default string"; size_t tmpStrSize = tmpStr.size(); // 2. 调用Go方法 printInGo(name, version, newStrPtr); // 假设printInGo是Go函数 // 3. 修改C++中的tmpStr和释放指针 // newStrPtr指向的内存被释放 // tmpStr被赋值为新字符串 tmpStr = new string("new string"); tmpStrSize = tmpStr.size(); // 4. 再次调用Go方法 printInGo(tmpStr, tmpStr, tmpStr); return 0;}

    Go侧代码

    package mainimport "C"import (    "fmt"    "time")func printInGo(p0 string, p1 string, p2 string) {    time.Sleep(10 * time.Second)    fmt.Printf("in go function, p0:%s size %d, p1:%s size %d, p2:%s size %d", p0, len(p0), p1, len(p1), p2, len(p2))}//export LoadModulefunc LoadModule(name string, version string, location string) int {    // 通过`make`创建内存拷贝    tmp3rdParam := make([]byte, len(location))    copy(tmp3rdParam, location)    new3rdParam := string(tmp3rdParam)        fmt.Println("LoadModule参数:", name, version, new3rdParam)    go printInGo(name, version, new3rdParam)    return 0}

    生成共享库

    go build -o libgoloader.so -buildmode=c-shared .

    程序执行结果

  • 初始状态:

    • C++中的tmpStr指针指向:"default string"
    • Go函数打印:"default string"的原始内容。
  • 修改C++中的tmpStr

    • tmpStr被赋值为:"new string"
    • 指针newStrPtr被释放
  • 再次调用Go函数:

    • Go函数打印的结果显示,p0p1的值与原始内容不符
    • p2的值仍然是原始内容

  • 结论

    结论

    C++调用Go方法时,字符串参数的内存管理需要由Go侧进行深度值拷贝。即参数三的处理方式。

    原因

    传入的字符串GoString实际是一个结构体,包含两个成员:

    • p: const char *指针
    • n: ptrdiff_t长度

    在C++中直接传递std::string对象的指针,会导致以下问题:

  • C++中的指针操作可能直接修改GoString结构体的p成员。
  • 当C++中的std::string对象被修改或重新赋值时,p指针指向的内存可能发生变化。
  • 解决方法

    在C++代码中,任何对GoString结构体p成员的操作都可能直接影响到Go中的字符串值。因此,必须通过单独的内存空间进行独立管理,避免C++中的指针操作对Go的影响。


    ps

    不在C++中进行内存申请释放的原因是:

    • C++无法感知Go中何时已经没有引用,无法找到合适的时间点进行内存释放。
    • 如果不进行深度拷贝,C++中的修改可能会影响Go中的原始数据。

    #summarize

    C++调用Go方法时,字符串参数的内存管理需要由Go侧进行深度值拷贝。

    转载地址:http://yvquz.baihongyu.com/

    你可能感兴趣的文章
    Netty核心模块组件
    查看>>
    Netty框架的服务端开发中创建EventLoopGroup对象时线程数量源码解析
    查看>>
    Netty源码—2.Reactor线程模型一
    查看>>
    Netty源码—4.客户端接入流程一
    查看>>
    Netty源码—4.客户端接入流程二
    查看>>
    Netty源码—5.Pipeline和Handler一
    查看>>
    Netty源码—6.ByteBuf原理二
    查看>>
    Netty源码—7.ByteBuf原理三
    查看>>
    Netty源码—7.ByteBuf原理四
    查看>>
    Netty源码—8.编解码原理二
    查看>>
    Netty源码解读
    查看>>
    Netty的Socket编程详解-搭建服务端与客户端并进行数据传输
    查看>>
    Netty相关
    查看>>
    Network Dissection:Quantifying Interpretability of Deep Visual Representations(深层视觉表征的量化解释)
    查看>>
    Network Sniffer and Connection Analyzer
    查看>>
    Net与Flex入门
    查看>>
    net包之IPConn
    查看>>
    NFinal学习笔记 02—NFinalBuild
    查看>>
    NFS共享文件系统搭建
    查看>>
    nfs复习
    查看>>