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

本文共 2600 字,大约阅读时间需要 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 main
    import "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 LoadModule
    func 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/

    你可能感兴趣的文章
    Objective-C实现Collatz 序列算法(附完整源码)
    查看>>
    Objective-C实现comb sort梳状排序算法(附完整源码)
    查看>>
    Objective-C实现combinationSum组合和算法(附完整源码)
    查看>>
    Objective-C实现combinations排列组合算法(附完整源码)
    查看>>
    Objective-C实现combine With Repetitions结合重复算法(附完整源码)
    查看>>
    Objective-C实现combine Without Repetitions不重复地结合算法(附完整源码)
    查看>>
    Objective-C实现conjugate gradient共轭梯度算法(附完整源码)
    查看>>
    Objective-C实现connected components连通分量算法(附完整源码)
    查看>>
    Objective-C实现Connected Components连通分量算法(附完整源码)
    查看>>
    Objective-C实现Convex hull凸包问题算法(附完整源码)
    查看>>
    Objective-C实现convolution neural network卷积神经网络算法(附完整源码)
    查看>>
    Objective-C实现convolve卷积算法(附完整源码)
    查看>>
    Objective-C实现coulombs law库仑定律算法(附完整源码)
    查看>>
    Objective-C实现counting sort计数排序算法(附完整源码)
    查看>>
    Objective-C实现countSetBits设置位的数量算法(附完整源码)
    查看>>
    Objective-C实现currency converter货币换算算法(附完整源码)
    查看>>
    Objective-C实现cycle sort循环排序算法(附完整源码)
    查看>>
    Objective-C实现data transformations数据转换算法(附完整源码)
    查看>>
    Objective-C实现datamatrix二维码识别 (附完整源码)
    查看>>
    Objective-C实现DateToDay 方法算法(附完整源码)
    查看>>