C++調(diào)用Go方法的字符串傳遞問(wèn)題及解決方案
摘要:C++調(diào)用Go方法時(shí),字符串參數(shù)的內(nèi)存管理需要由Go側(cè)進(jìn)行深度值拷貝。
現(xiàn)象
在一個(gè)APP技術(shù)項(xiàng)目中,子進(jìn)程按請(qǐng)求加載Go的ServiceModule,將需要拉起的ServiceModule信息傳遞給Go的Loader,存在C++調(diào)用Go方法,傳遞字符串的場(chǎng)景。
方案驗(yàn)證時(shí),發(fā)現(xiàn)有奇怪的將std::string對(duì)象的內(nèi)容傳遞給Go方法后,在Go方法協(xié)程中取到的值與預(yù)期不一致。
經(jīng)過(guò)一段時(shí)間的分析和驗(yàn)證,終于理解問(wèn)題產(chǎn)生的原因并給出解決方案,現(xiàn)分享如下。
背景知識(shí)
- Go有自己的內(nèi)存回收GC機(jī)制,通過(guò)make等申請(qǐng)的內(nèi)存不需要手動(dòng)釋放。
- C++中為std::string變量賦值新字符串后,.c_str()和.size()的結(jié)果會(huì)聯(lián)動(dòng)變化,尤其是.c_str()指向的地址也有可能變化。
- go build -buildmode=c-shared .生成的.h頭文件中定義了C++中Go的變量類(lèi)型的定義映射關(guān)系,比如GoString、GoInt等。其中GoString實(shí)際是一個(gè)結(jié)構(gòu)體,包含一個(gè)字符指針和一個(gè)字符長(zhǎng)度。
原理及解釋
通過(guò)代碼示例方式解釋具體現(xiàn)象及原因,詳見(jiàn)注釋
C++側(cè)代碼:
// // Created by w00526151 on 2020/11/5. // #include <string> #include <iostream> #include <unistd.h> #include "libgoloader.h" /** * 構(gòu)造GoString結(jié)構(gòu)體對(duì)象 * @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()); { //通過(guò)new新申請(qǐng)一段內(nèi)存做字符串拷貝 char *newStrPtr = NULL; int newStrSize = tmpStr.size(); newStrPtr = new char[newStrSize]; tmpStr.copy(newStrPtr, newStrSize, 0); //調(diào)用Go方法,第一個(gè)參數(shù)直接傳std::string的c_str指針和大小,第二個(gè)參數(shù)傳在C++中單獨(dú)申請(qǐng)的內(nèi)存并拷貝的字符串指針,第三個(gè)參數(shù)和第一個(gè)一樣,但是在go代碼中做內(nèi)存拷貝保存。 //調(diào)用Go方法后,通過(guò)賦值修改std::string的值內(nèi)容,等待Go中新起的線程10s后再將三個(gè)參數(shù)值打印出來(lái)。 LoadModule(buildGoString(tmpStr.c_str(), tmpStr.size()), buildGoString(newStrPtr, newStrSize), buildGoString(tmpStr.c_str(),tmpStr.size())); //修改tmpStr的值,tmpStr.c_str()得到的指針指向內(nèi)容會(huì)變化,tmpStr.size()的值也會(huì)變化,Go中第一個(gè)參數(shù)也會(huì)受到影響,前幾位會(huì)變成新字符串內(nèi)容。 //由于在Go中int是值拷貝,所以在Go中,第一個(gè)參數(shù)的長(zhǎng)度沒(méi)有變化,因此實(shí)際在Go中已經(jīng)出現(xiàn)內(nèi)存越界訪問(wèn),可能產(chǎn)生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()); //釋放新申請(qǐng)的newStrPtr指針,Go中對(duì)應(yīng)第二個(gè)string變量?jī)?nèi)存也會(huì)受到影響,產(chǎn)生亂碼。 // 實(shí)際在Go中,已經(jīng)在訪問(wèn)一段在C++中已經(jīng)釋放的內(nèi)存,屬于野指針訪問(wèn),可能產(chǎn)生Coredump。 delete newStrPtr; } pause(); }
Go側(cè)代碼:
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 { //通過(guò)make的方式,新構(gòu)建一段內(nèi)存來(lái)存放從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側(cè)代碼通過(guò)-buildmode=c-shared的方式生成libgoloader.so及l(fā)ibgoloader.h供C++編譯運(yùn)行使用
go build -o libgoloader.so -buildmode=c-shared .
程序執(zhí)行結(jié)果:
test send string to go in C++
in C++ tmpStr: 0x7fffe1fb93f0, tmpStr: /tmp/udsgateway-netconftemplateservice, tmpStr.size:38
# 將C++的指針傳給Go,一開(kāi)始打印都是OK的
in go loadModule,first param is /tmp/udsgateway-netconftemplateservice second param is /tmp/udsgateway-netconftemplateservice third param is /tmp/udsgateway-netconftemplateservice
# 在C++中,將指針指向的內(nèi)容修改,或者刪掉指針
in C++ change tmpStr and delete newStrPtr, new tmpStr: 0x7fffe1fb93f0, tmpStr: new string, tmpStr.size:10
# 在Go中,參數(shù)1、參數(shù)2對(duì)應(yīng)的Go string變量都受到了影響,參數(shù)3由于做了深度拷貝,沒(méi)有受到影響。
in go function, p0:new string eway-netconftemplateservice size 38, p1: p��� netconftemplateservice size 38, p2:/tmp/udsgateway-netconftemplateservice size 38
結(jié)論
- 結(jié)論:C++調(diào)用Go方法時(shí),字符串參數(shù)的內(nèi)存管理需要由Go側(cè)進(jìn)行深度值拷貝。即參數(shù)三的處理方式
- 原因:傳入的字符串GoString,實(shí)際是一個(gè)結(jié)構(gòu)體,第一個(gè)成員p是一個(gè)char*指針,第二個(gè)成員n是一個(gè)int長(zhǎng)度。
在C++代碼中,任何對(duì)成員p的char*指針的操作,都將直接影響到Go中的string對(duì)象的值。
只有通過(guò)單獨(dú)的內(nèi)存空間開(kāi)辟,進(jìn)行獨(dú)立內(nèi)存管理,才可以避免C++中的指針操作對(duì)Go的影響。
ps:不在C++中進(jìn)行內(nèi)存申請(qǐng)釋放的原因是C++無(wú)法感知Go中何時(shí)才能真的已經(jīng)沒(méi)有對(duì)象引用,無(wú)法找到合適的時(shí)間點(diǎn)進(jìn)行內(nèi)存釋放。
本文分享自華為云社區(qū)《C++調(diào)用Go方法的字符串傳遞問(wèn)題及解決方案》,原文作者:王芾。
到此這篇關(guān)于C++調(diào)用Go方法的字符串傳遞問(wèn)題及解決方案的文章就介紹到這了,更多相關(guān)C++調(diào)用Go字符串傳遞內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- C++生成dll和調(diào)用dll的方法實(shí)例
- Python調(diào)用C/C++動(dòng)態(tài)鏈接庫(kù)的方法詳解
- C/C++函數(shù)調(diào)用的幾種方式總結(jié)
- C++調(diào)用C#的DLL實(shí)現(xiàn)方法
- Python 調(diào)用VC++的動(dòng)態(tài)鏈接庫(kù)(DLL)
- c++生成dll使用python調(diào)用dll的方法
- PHP中調(diào)用C/C++制作的動(dòng)態(tài)鏈接庫(kù)的教程
- Lua教程(四):在Lua中調(diào)用C語(yǔ)言、C++的函數(shù)
- php調(diào)用c++的方法
相關(guān)文章
C++算術(shù)運(yùn)算符與類(lèi)型轉(zhuǎn)換
這篇文章主要介紹了C++算術(shù)運(yùn)算符與類(lèi)型轉(zhuǎn)換,C++當(dāng)中提供5種基礎(chǔ)的算術(shù)運(yùn)算符,分別是加法、減法、乘法、除法和取模。下main我們就一起來(lái)看看下面文章得具體舉例與說(shuō)明,需要的朋友可以參考一下,希望對(duì)你有所幫助2021-11-11C++11用兩個(gè)線程輪流打印整數(shù)的實(shí)現(xiàn)方法
這篇文章主要介紹了C++11用兩個(gè)線程輪流打印整數(shù)的實(shí)現(xiàn)方法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09C++ Qt開(kāi)發(fā)之使用QHostInfo查詢主機(jī)地址
Qt 是一個(gè)跨平臺(tái)C++圖形界面開(kāi)發(fā)庫(kù),利用Qt可以快速開(kāi)發(fā)跨平臺(tái)窗體應(yīng)用程序,本文將重點(diǎn)介紹如何運(yùn)用QHostInfo組件實(shí)現(xiàn)對(duì)主機(jī)地址查詢功能,希望對(duì)大家有所幫助2024-03-03for循環(huán)中刪除map中的元素valgrind檢測(cè)提示error:Invalid read of size 8
這篇文章主要介紹了for循環(huán)中刪除map中的元素valgrind檢測(cè)提示error:Invalid read of size 8 的相關(guān)資料,需要的朋友可以參考下2016-07-07C++ OpenCV學(xué)習(xí)之圖像金字塔與圖像融合詳解
圖像金字塔分為兩種:高斯金字塔和拉普拉斯金字塔。圖像金字塔在保持細(xì)節(jié)的條件下進(jìn)行圖像融合等多尺度編輯操作非常有用。本文將利用圖像金字塔實(shí)現(xiàn)圖像融合,需要的可以參考一下2022-03-03Qt開(kāi)發(fā)實(shí)現(xiàn)跨窗口信號(hào)槽通信
這篇文章主要為大家詳細(xì)介紹了Qt開(kāi)發(fā)實(shí)現(xiàn)跨窗口信號(hào)槽通信,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01