本文共 2600 字,大约阅读时间需要 8 分钟。
在一个技术项目中,我们需要在C++代码中调用Go方法,传递字符串参数。经过一系列的验证和分析,我们发现了一个棘手的问题,并最终找到了解决方案。以下是详细的分析过程和解决方法。
在实际项目中,我们需要将需要拉起的ServiceModule信息传递给Go的Loader。在C++代码中,通过调用Go方法传递字符串参数时,发现以下问题:
Go的内存管理机制
Go语言自带内存回收机制(GC),通过make等函数申请的内存不需要手动释放。这意味着在C++代码中直接传递内存指针可能带来安全隐患。 C++字符串对象的内存布局
在C++中,为std::string变量赋值新字符串后,.c_str()和.size()的结果会联动变化。尤其是,当字符串内容发生变化时,.c_str()指向的地址也会发生变化。 Go与C++之间的内存交互
使用-buildmode=c-shared生成的共享库文件中定义了C++中Go变量的类型映射关系。例如,GoString实际是一个结构体,包含一个字符指针和一个长度成员。 通过代码示例解释具体现象及原因:
// 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;}
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 .
初始状态:
tmpStr指针指向:"default string"修改C++中的tmpStr:
tmpStr被赋值为:"new string"newStrPtr被释放再次调用Go函数:
p0和p1的值与原始内容不符p2的值仍然是原始内容C++调用Go方法时,字符串参数的内存管理需要由Go侧进行深度值拷贝。即参数三的处理方式。
传入的字符串GoString实际是一个结构体,包含两个成员:
p: const char *指针n: ptrdiff_t长度在C++中直接传递std::string对象的指针,会导致以下问题:
GoString结构体的p成员。std::string对象被修改或重新赋值时,p指针指向的内存可能发生变化。在C++代码中,任何对GoString结构体p成员的操作都可能直接影响到Go中的字符串值。因此,必须通过单独的内存空间进行独立管理,避免C++中的指针操作对Go的影响。
不在C++中进行内存申请释放的原因是:
#summarize
C++调用Go方法时,字符串参数的内存管理需要由Go侧进行深度值拷贝。转载地址:http://yvquz.baihongyu.com/