摘要
在Go和C内存共享时,可能会出现错误cgo argument has Go pointer to Go pointer。该问题出现的主要原因是在Go1.6.2版本之后,GO语言的rumtime检查机制对C和GO之间的指针传递行为进行了强制检查,不允许传递指向Go内存地址的指针。
正文
在Go1.6.2版本之后,Go的runtime加入了指针违规传递检测机制。该机制主要针对Go向C传递带有指向其他Go内存的地址,具体见文献检查机制。
- 情况1 不能传递带有初始化内存空间的指针切片
package main
//#cgo CFLAGS: -std=c99
/*
#include <stdio.h>
#include <stdlib.h>
void array(int** arr,int n){
for (int i=0;i<n;i++){
*arr=(int*)malloc(sizeof(int));
**arr=i+i;
arr++;
}
}
*/
import "C"
import (
"fmt"
"runtime"
"unsafe"
)
func main() {
fmt.Printf("Allocate a memory through Golang coding.\n");
c:=make([]*C.int,3);
for i,_:=range c{
fmt.Println(i,c[i])
}
//"Pass the first pointer of the memory to C code to process.
C.array((**C.int)(unsafe.Pointer(&c[0])),3);
fmt.Printf("Display the processing result.\n")
for i,_:=range c{
fmt.Println(i,c[i],*c[i]);
}
runtime.GC();
}
执行结果:
package main
//#cgo CFLAGS: -std=c99
/*
#include <stdio.h>
#include <stdlib.h>
void array(int** arr,int n){
for (int i=0;i<n;i++){
*arr=(int*)malloc(sizeof(int));
**arr=i+i;
arr++;
}
}
*/
import "C"
import (
"fmt"
"runtime"
"unsafe"
)
func main() {
fmt.Printf("Allocate a memory through Golang coding.\n");
c:=make([]*C.int,3);
for i,_:=range c{
c[i]=new(C.int)
fmt.Println(i,c[i])
}
//"Pass the first pointer of the memory to C code to process.
C.array((**C.int)(unsafe.Pointer(&c[0])),3);
fmt.Printf("Display the processing result.\n")
for i,_:=range c{
fmt.Println(i,c[i],*c[i]);
}
runtime.GC();
}
执行结果:
- 情况2 Go不能向C传递带有初始化内存空间的结构体
package main
//#cgo CFLAGS: -std=c99
/*
#include <stdio.h>
#include <stdlib.h>
typedef struct{
int* Number;
}A;
void transfer(A *a){
if (a->Number==NULL){
a->Number=(int*)malloc(sizeof(int));
}
*(a->Number)=4;
}
*/
import "C"
import (
"fmt"
"unsafe"
)
func main() {
var S A
fmt.Printf("*****before being transfered to C!\n");
S.Number=new(C.int)
fmt.Println(S.Number)
C.transfer((*C.A)(unsafe.Pointer(&S)))
fmt.Printf("*****after being transfered to C!\n");
fmt.Println(*S.Number)
}
type A struct{
Number *C.int
}
执行结果
- 情况3 不能传递指向已知内存的指针给C语言
package main
//#cgo CFLAGS: -std=c99
/*
#include <stdio.h>
#include <stdlib.h>
typedef struct{
int* Number;
}A;
void transfer(int **a){
printf("*=%p,**a=%d\n",*a,**a);
printf("fix value of point to 16\n");
**a=16;
}
*/
import "C"
import "fmt"
func main() {
point:=new(C.int);
*point=4;
fmt.Println("******before being transfered to C Code!")
fmt.Printf("point=%p,*point=%d\n",point,*point)
C.transfer(&point);
fmt.Printf("point=%p,*point=%d\n",point,*point)
}
执行结果为
解决办法
解决办法主要有两类,临时性解决和永久性解决。临时性解决主要针对某次编译绕开检测,缺点是每次执行都要带参数GODEBUG=cgocheck=0这个标识;永久性解决则将该GODEBUG=cgocheck=0做成系统环境变量,使每次运行都自动调用该参数。
- 临时性解决
在编译开始时,加入GODEBUG=cgocheck=0,绕开rutime的编译检查机制,具体执行方法如下所示:
- 永久性解决
将GODEGUG=cgocheck=0,加入Linux系统的~/.bashrc文件的末尾,实现启动默认添加该变量。
执行结果为
总结
在CGO交互中,出现报错cgo argument has Go pointer to Go pointer情形的主要原因是,GO语言的rumtime检查机制对C和GO之间的指针传递行为进行了强制检查。绕开检查机制是目前已知的处理方式。其实,为了避免导致不必要的内存泄露,尽量不用上述的指针传递方式进行C语言和GO的内存共享。