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

现象

在一个APP技术项目中,子进程按请求加载Go的ServiceModule,将需要拉起的ServiceModule信息传递给Go的Loader,存在C++调用Go方法,传递字符串的场景。

方案验证时,发现有奇怪的将std::string对象的内容传递给Go方法后,在Go方法协程中取到的值与预期不一致。

经过一段时间的分析和验证,终于理解问题产生的原因并给出解决方案,现分享如下。

背景知识

  1. Go有自己的内存回收GC机制,通过make等申请的内存不需要手动释放。
  2. C++中为std::string变量赋值新字符串后,.c_str()和.size()的结果会联动变化,尤其是.c_str()指向的地址也有可能变化。
  3. go build -buildmode=c-shared .生成的.h头文件中定义了C++中Go的变量类型的定义映射关系,比如GoString、GoInt等。其中GoString实际是一个结构体,包含一个字符指针和一个字符长度。

原理及解释

通过代码示例方式解释具体现象及原因,详见注释

C++侧代码:

//
// Created by w00526151 on 2020/11/5.
// #include <string>
#include <iostream>
#include <unistd.h>
#include "libgoloader.h" /**
* 构造GoString结构体对象
* @param p
* @param n
* @return
*/
GoString buildGoString(const char* p, size_t n){
//typedef struct { const char *p; ptrdiff_t n; } _GoString_;
//typedef _GoString_ GoString;
return {p, static_cast<ptrdiff_t>(n)};
} int main(){
std::cout<<"test send string to go in C++"<<std::endl; std::string tmpStr = "/tmp/udsgateway-netconftemplateservice";
printf("in C++ tmpStr: %p, tmpStr: %s, tmpStr.size:%lu \r\n", tmpStr.c_str(), tmpStr.c_str(), tmpStr.size());
{
//通过new新申请一段内存做字符串拷贝
char *newStrPtr = NULL;
int newStrSize = tmpStr.size();
newStrPtr = new char[newStrSize];
tmpStr.copy(newStrPtr, newStrSize, 0); //调用Go方法,第一个参数直接传std::string的c_str指针和大小,第二个参数传在C++中单独申请的内存并拷贝的字符串指针,第三个参数和第一个一样,但是在go代码中做内存拷贝保存。
//调用Go方法后,通过赋值修改std::string的值内容,等待Go中新起的线程10s后再将三个参数值打印出来。
LoadModule(buildGoString(tmpStr.c_str(), tmpStr.size()), buildGoString(newStrPtr, newStrSize), buildGoString(tmpStr.c_str(),tmpStr.size()));
//修改tmpStr的值,tmpStr.c_str()得到的指针指向内容会变化,tmpStr.size()的值也会变化,Go中第一个参数也会受到影响,前几位会变成新字符串内容。
//由于在Go中int是值拷贝,所以在Go中,第一个参数的长度没有变化,因此实际在Go中已经出现内存越界访问,可能产生Coredump。
tmpStr = "new string";
printf("in C++ change tmpStr and delete newStrPtr, new tmpStr: %p, tmpStr: %s, tmpStr.size:%lu \r\n", tmpStr.c_str(), tmpStr.c_str(), tmpStr.size());
//释放新申请的newStrPtr指针,Go中对应第二个string变量内存也会受到影响,产生乱码。
// 实际在Go中,已经在访问一段在C++中已经释放的内存,属于野指针访问,可能产生Coredump。
delete newStrPtr;
}
pause();
}

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的方式,新构建一段内存来存放从C++处传入的字符串,深度拷贝防止C++中修改影响Go
tmp3rdParam := make([]byte, len(location))
copy(tmp3rdParam, location)
new3rdParam := string(tmp3rdParam)
fmt.Println("in go loadModule,first param is",name,"second param is",version, "third param is", new3rdParam)
go printInGo(name, version, new3rdParam);
return 0
}

Go侧代码通过-buildmode=c-shared的方式生成libgoloader.so及libgoloader.h供C++编译运行使用

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

程序执行结果:

test send string to go in C++
in C++ tmpStr: 0x7fffe1fb93f0, tmpStr: /tmp/udsgateway-netconftemplateservice, tmpStr.size:38
# 将C++的指针传给Go,一开始打印都是OK的
in go loadModule,first param is /tmp/udsgateway-netconftemplateservice second param is /tmp/udsgateway-netconftemplateservice third param is /tmp/udsgateway-netconftemplateservice
# 在C++中,将指针指向的内容修改,或者删掉指针
in C++ change tmpStr and delete newStrPtr, new tmpStr: 0x7fffe1fb93f0, tmpStr: new string, tmpStr.size:10
# 在Go中,参数1、参数2对应的Go string变量都受到了影响,参数3由于做了深度拷贝,没有受到影响。
in go function, p0:new string eway-netconftemplateservice size 38, p1: p��� netconftemplateservice size 38, p2:/tmp/udsgateway-netconftemplateservice size 38

结论

  • 结论:C++调用Go方法时,字符串参数的内存管理需要由Go侧进行深度值拷贝。即参数三的处理方式
  • 原因:传入的字符串GoString,实际是一个结构体,第一个成员p是一个char*指针,第二个成员n是一个int长度。

在C++代码中,任何对成员p的char*指针的操作,都将直接影响到Go中的string对象的值。

只有通过单独的内存空间开辟,进行独立内存管理,才可以避免C++中的指针操作对Go的影响。

ps:不在C++中进行内存申请释放的原因是C++无法感知Go中何时才能真的已经没有对象引用,无法找到合适的时间点进行内存释放。

本文分享自华为云社区《C++调用Go方法的字符串传递问题及解决方案》,原文作者:王芾。

点击关注,第一时间了解华为云新鲜技术~

C++调用Go方法的字符串传递问题及解决方案的更多相关文章

  1. WebView调用js方法获取返回值的完美解决方案

    在Android项目中我们或多或少会涉及到与js交互的问题,这其中WebView是必须掌握的控件,今天主要说说我们通过WebView调用js方法,然后如何很好的获取返回值.这里我总结了三种方式,大家可 ...

  2. 【问题】Asp.net MVC 的cshtml页面中调用JS方法传递字符串变量参数

    [问题]Asp.net MVC 的cshtml页面中调用JS方法传递字符串变量参数. [解决]直接对变量加引号,如: <button onclick="deleteProduct('@ ...

  3. Groovy小结:java调用Groovy方法并传递参数

    Groovy小结:java调用Groovy方法并传递参数 @(JAVA总结) 1. 场景描述 在网上查了资料发现,java有三种方式调用groovy脚本.但是真正在实际的服务器环境中,嵌入groovy ...

  4. 利用Ajax调用controller方法并传递参数

    一.背景由于近期工作需要将人脸识别功能与选课系统结合,但是对前端知识了解的很少,只能边做边学了,因此在这边把遇到的一些坑说明一下,希望能帮助到像我一样的初学者 二.具体内容这里采用框架为MVC,如果想 ...

  5. GET方法和POST方法的区别,Get方法到底可传递的字符串的最大长度是多少?

    GET方法和POST方法的区别,Get方法到底可传递的字符串的最大长度是多少?曾经人介绍,如果使用GET方式传输参数,URL的最大长度是256个字节,对此深信不疑. 但是最近看到一些超长的url,能够 ...

  6. odoo14 button 事件调用python方法如何传递参数

    1 <field name="user_ids" 2 mode="kanban" 3 nolabel="1" 4 options=&q ...

  7. java中由类名和方法名字符串实现其调用【反射机制】

    js里通过eval()函数,在知道某个方法名是可以实现调用该方法,那么在java里边又怎么实现的呢? java里边是通过反射机制来实现,代码如下: import java.lang.reflect.M ...

  8. Xilium.CefGlue利用XHR实现Js调用c#方法

    防外链 博客园原文地址在这里http://www.cnblogs.com/shen6041/p/3442499.html 引 Xilium CefGlue是个不错的cef扩展工程,托管地址在这里 ht ...

  9. Java 反射 Method的invoke回调调用任意方法

    Java 反射 Method的invoke回调调用任意方法 @author ixenos 关键子:Method.Field.invoke方法指针/函数指针.回调函数 invoke回调流程示例 0.由C ...

随机推荐

  1. 学不动了!微信官方推出 Web 前端和小程序统一框架 Kbone

    听说最近微信官方推出了一个统一 Web 前端和小程序的框架 -- Kbone ,特意去看了下... 为什么微信要搞Kbone? 微信小程序的底层模型和 Web 端不同,开发者无法直接把 Web 端的代 ...

  2. Jmeter请求之接口串联自动化测试(未完)

    方案一:添加Cookie管理器,把用户的登录状态存在cookie管理器中,类似于浏览器 存储测试结果: 监听器->保存响应到文件,对结果进行存储 文件名前缀:保存到哪个地方前缀是什么D:\tes ...

  3. Docker Stack 笔记

    Docker Compose (Docker Stack) image: Specify the image to start the container from. Can either be a ...

  4. Linux下批量kill掉进程

    ps -ef|grep java|grep -v grep|cut -c 9-15|xargs kill -9 管道符"|"用来隔开两个命令,管道符左边命令的输出会作为管道符右边命 ...

  5. jquery $.ajax 获取josn数据

    <script type="text/javascript" src="jquery-1.9.1.js"></script> <s ...

  6. Python基础数据类型方法补充

    str 补充的方法: capitalize():首字母大写,其余变小写 s = 'liBAI' s1 = s.capitalize() print(s1) # Libai swapcase():大小写 ...

  7. C++学习笔记---指针

    1.基本概念 作用:可以通过指针直接访问内存 内存编号是从0开始记录的,一般用16进制数字表示 可以利用指针变量保存地址 2.定义和使用 定义指针 他们的关系就是这样的 运行输出 使用指针 运行输出会 ...

  8. vscode按下F5黑窗口显示的是乱码

    找了很多方法,powershell修改了也变不了 最后找到了这个链接的方法:https://blog.csdn.net/weixin_40040107/article/details/10372155 ...

  9. 3号随笔,搭建web环境

    创建数据库可能会遇到的问题 数据库语法错误: 如果写错了就会报错. 搭建web项目,我采用了MVC结构搭建 模型层写数据包装,controller层写业务代码,service写数据库内容,创建之后就搭 ...

  10. 两个有序数列找第k小

    给定一个数组,数组中的数据无序,在一个数组中找出其第k个最小的数,例如对于数组x,x = {3,2,1,4,5,6},则其第2个最小的数为2  两个有序数组 找第k小 * 方案一 合并遍历 * 二:游 ...