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

现象

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

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

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

背景知识

Go有自己的内存回收GC机制,通过make等申请的内存不需要手动释放。

C++中为std::string变量赋值新字符串后,.c_str()和.size()的结果会联动变化,尤其是.c_str()指向的地址也有可能变化。

go build -buildmode=c-shared .生成的.h头文件中定义了C++中Go的变量类型的定义映射关系,比如GoString、GoInt等。其中GoString实际是一个结构体,包含一个字符指针和一个字符长度。

原理及解释

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

C++侧代码:

//

// Created by w00526151 on 2020/11/5.

//

#include

#include

#include

#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(n)};

}

int main(){

std::cout<

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方法的字符串传递问题及解决方案的文章就介绍到这了,更多相关C++调用Go字符串传递内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

c++代码转为go_C++调用Go方法的字符串传递问题及解决方案相关推荐

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

    摘要:C++调用Go方法时,字符串参数的内存管理需要由Go侧进行深度值拷贝. 现象 在一个APP技术项目中,子进程按请求加载Go的ServiceModule,将需要拉起的ServiceModule信息 ...

  2. JAVA——Scanner类绑定System.in后调用close()方法所引发的错误及其解决方案

    基本概念 Java通过系统类System实现标准输入/输出的功能 通过实例化Scanner类的方式实现从键盘的数据输入 问题描述 错误示例一: import java.util.Scanner;pub ...

  3. c++代码转为go_Go调用C/C++

    cgo golang是类C的语言 支持调用C接口(不支持调用C++) Go调用C/C++的方式 : C : 直接调用C API C++ : 通过实现一层封装的C接口来调用C++接口 Go集成C/C++ ...

  4. c++代码转为go_C++程序员是如何评价GO的

    原标题:C++程序员是如何评价GO的 作者丨Murray 翻译丨王江平 此文主要对GO语言的简单语法做了详细描述,并与C.C++.Java作了比较,以下为译文. 我正在读由Brian Kernigha ...

  5. 【Android NDK 开发】JNI 方法解析 ( C/C++ 调用 Java 方法 | 函数签名 | 调用对象方法 | 调用静态方法 )

    文章目录 I . 调用 Java 方法流程 II . 获取 jclass 对象 ( GetObjectClass ) III . 获取 jclass 对象 ( FindClass ) IV . JNI ...

  6. C#中调用python方法

    最近因为项目设计,有部分使用Python脚本,因此代码中需要调用python方法. 1.首先,在c#中调用python必须安装IronPython,在  http://ironpython.codep ...

  7. java中的就近原则、方法中值传递和引用传递的区别、什么是构造方法、this关键字用法、什么是封装

    你知道java中的就近原则嘛? package com.Test.java; /*** * 在java里面有一个"就近原则"详情可以参照下面代码**/public class Ac ...

  8. append方法实现字符串的拼接

    package com.it.pinjieString; /*需求:定义一个方法,把int数组中的数据按照指定的格式拼接成一个字符串进行返回,调用该方法,并在控制台输出结果, //例如,数组为int[ ...

  9. 如何将matlab代码转为C语言(2)--在C++中调用matlab的函数

    如何将matlab代码转为C语言(2)–在C++中调用matlab的函数 在上一条博文中提供了一种直接在matlab操作中的方法,下面提供一种新的调用方法,即在C++中调用matlab中的dll文件. ...

最新文章

  1. 未来几年,BCH超越BTC的路径是什么?
  2. JSONP实现Ajax跨域请求
  3. python 访问sas 逻辑库,SAS | 逻辑库和SAS数据集
  4. (十)nodejs循序渐进-高性能游戏服务器框架pomelo之介绍和安装篇
  5. DCOM EXCE权限配置问题
  6. 专访探真科技:云原生安全与业务迭代平衡术
  7. ps语义分割_一键抠图,毛发毕现:这个GitHub项目助你快速PS
  8. 影响网络电视直播清晰度的四个因素
  9. java web 编程技术 答案_《javaweb编程技术》课后习题答案.docx
  10. PB的特点及Powerscript的语言基础
  11. 基于臻图ZTMAP 3DGIS平台打造线上线下融合的智慧展览中心
  12. 实践系列:分销平台的技术架构
  13. apple pay代码实现
  14. linux脚本 输出双引号,Linux Shell中三种引号的用法及区别
  15. 用酒精,湿巾,擦笔记本电脑/键盘,然后触摸板就不能用了?什么情况?
  16. 动态域名解析--每步动态域名解析
  17. 创维4k电视测试软件,创维4色4K真牛 国产硬件最强电视评测!
  18. neu ikobikob
  19. Android 之 设置屏幕常亮
  20. android代码连接wifi

热门文章

  1. 2017.10.15 旅行comf 失败总结
  2. c语言运行后出现xt073,2017年北京工业大学城市交通学院894C语言与数据结构之C程序设计考研强化模拟题...
  3. 两波形相位差的计算值_如何将您的计算机用作任意波形发生器
  4. 易语言数据类型与c 对照,易语言利用自定义数据类型和数组. 制作键对值操作类/内存配置...
  5. 格式化代码php,格式化php代码的两种方法
  6. 常用的linux文件权限
  7. 判断两个时间段是否重叠的算法
  8. 利用redis-sentinel+keepalived实现redis高可用
  9. pyplot 画多个图时搅合到了一起_这些认数字游戏,宝宝最喜欢,家长可以和宝宝一起玩...
  10. uart串口通信_听说UART与STM32的HAL库更配哦