Abstract: when C++ calls the Go method, the memory management of string parameters needs to be deeply copied by the Go side.
The phenomenon of
In an APP technology project, the child process loads the ServiceModule for Go as requested and passes the ServiceModule information that needs to be pulled to the Go Loader. There are scenarios where C++ calls the Go method and passes the string.
During scheme validation, it was found that the value of the Go method coroutine was inconsistent with the expected value after passing the contents of STD :: String to the Go method.
After a period of analysis and verification, I finally understand the causes of the problem and propose solutions, which are shared as follows.
Background knowledge
- Go has its own memory reclamation GC mechanism, so memory requested by make etc does not need to be freed manually.
- In C++, when assigning a new string to STD ::string, the result of.c_str() and.size() will change together, especially the address to which.c_str() points.
- go build -buildmode=c-shared . The generated.h header file defines the definition mapping of C++ Go variable types, such as GoString, GoInt, etc. Where GoString is actually a structure containing a character pointer and a character length.
Principle and Explanation
Code examples are used to explain the specific symptoms and causes. For details, see comments
C++ side code:
// // Created by w00526151 on 2020/11/5. // #include #include #include <unistd.h> #include “libgoloader.h”
* @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 rn", tmpStr.c_str(), tmpStr.c_str(), tmpStr.size()); Char *newStrPtr = NULL; int newStrSize = tmpStr.size(); newStrPtr = new char[newStrSize]; tmpStr.copy(newStrPtr, newStrSize, 0); The first argument is passed to STD ::string's c_str pointer and size. The second argument is passed to C++ separately requested memory and copied string pointer. The third argument is the same as the first one, but is copied in the Go code. // After the Go method is called, the value of STD ::string is modified by assignment, and the three parameter values are printed 10 seconds after the new thread of Go is started.Copy the code
LoadModule(buildGoString(tmpStr.c_str(), tmpStr.size()), buildGoString(newStrPtr, newStrSize), buildGoString(tmpStr.c_str(),tmpStr.size())); // If the value of tmpStr is changed, the pointer to tmpstr.c_str () will change, as will the value of tmpstr.size (). The first argument in Go will also be affected, and the first bits will become the new string. // Since int is a value copy in Go, the length of the first parameter in Go does not change, so the actual memory access has occurred in Go, which may produce Coredump. tmpStr = “new string”; printf(“in C++ change tmpStr and delete newStrPtr, new tmpStr: %p, tmpStr: %s, tmpStr.size:%lu rn”, tmpStr.c_str(), tmpStr.c_str(), tmpStr.size()); // Release the pointer to newStrPtr, and the memory of the second string variable in Go will also be affected. // in Go, we are accessing a memory that has been freed in C++. This is a wild pointer access, which may produce a Coredump. delete newStrPtr; } pause(); }
Go side code:
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 a new memory to store the string passed in from 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 }Copy the code
Libgoloader. so and libgoloader.h are generated by -buildmode=c-shared
go build -o libgoloader.so -buildmode=c-shared .
Copy the code
Program execution result:
test send string to go in C++ in C++ tmpStr: 0x7fffe1fb93f0, tmpStr: / TMP /udsgateway- netConfTemplateservice, tmpstr. size:38 # In go loadModule,first param is/TMP/udsGateway – netConfTemplateservice second param is / TMP /udsgateway- netConfTemplateservice third param is/TMP /udsgateway- netConfTemplateservice In C++ change tmpStr and delete newStrPtr, new tmpStr: 0x7fffe1fb93f0, tmpStr: New string, tmpstr. size:10 # In Go, the variables corresponding to parameter 1 and parameter 2 are affected. Parameter 3 is not affected due to the deep copy. in go function, p0:new string eway-netconftemplateservice size 38, p1: P �� netConfTemplateservice size 38, p2:/ TMP /udsgateway- netConfTemplateservice size 38
conclusion
- ** conclusion: when C++ calls the Go method, the memory management of string parameters needs to be deep-value copied by the Go side. That is, the treatment of parameter three
- The first member p is a char* pointer, and the second member N is an int length.
In C++ code, any manipulation of a char* pointer to member p directly affects the value of the string in Go.
Only through a separate memory space, independent memory management, can avoid the influence of C++ pointer operations on Go.
Ps: the reason why memory is not freed in C++ is that C++ cannot sense when there are no object references in Go and cannot find the appropriate point in time to free memory.
This article is shared by huawei cloud community “C++ call Go method string transfer problems and solutions”, original author: wang fu.
Click to follow, the first time to learn about Huawei cloud fresh technology ~