分享
  1. 首页
  2. 文章

cgo的指针传递

p了个c · · 3357 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

在cgo的官方文档中有一小节特地介绍了cgo中传递c语言和go语言指针之间的传递,由于里面讲得比较抽象并且缺少例子,因此通过这篇文章总结cgo指针传递的注意事项。

基本概念

在官方文档和本篇总结中,Go指针指的是指向Go分配的内存的指针(例如使用&运算符或者调用new函数获取的指针)。而C指针指的是C分配的内存的指针(例如调用malloc函数获取的指针)。一个指针是Go指针还是C指针,是根据内存如何分配判断的,与指针的类型无关。

Go调用C

传递指向Go Memory的指针

Go调用C Code时,Go传递给C Code的Go指针所指的Go Memory中不能包含任何指向Go Memory的Pointer。

值得注意的是,Go是可以传递给C Code的Go指针的,但是这个指针里面不能包含任何指向Go Memory的Pointer。

package main
/*
#include <stdio.h>
struct Foo {
 int a;
 int *p;
};
void plusOne(struct Foo *f) {
 (f->a)++;
 *(f->p)++;
}
*/
import "C"
import "unsafe"
import "fmt"
func main() {
 f := &C.struct_Foo{}
 f.a = 5
 f.p = (*C.int)((unsafe.Pointer)(new(int)))
 // f.p = &f.a
 C.plusOne(f)
 fmt.Println(int(f.a))
}

在以上代码可以看出,Go Code向C Code传递了一个指向Go Memory(Go分配的)指针f,但f指向的Go Memory中有一个指针p指向了另一处Go Memory:new(int)。当使用go build编译这个文件时,是可以通过编译的,然后在运行时会发生如下报错:panic runtime error: cgo argument has Go pointer to Go pointer

传递指向struc field的指针

Go调用C Code时,如果传递的是一个指向struct field的指针,那么"Go Memory"专指这个field所占用的内存,即便struct中有其他field指向其他Go Memory也没关系。

将上面例子改为只传入指向struct field的指针。如下:

package main
/*
#include <stdio.h>
struct Foo {
 int a;
 int *p;
};
void plusOne(int *i) {
 (*i)++;
}
*/
import "C"
import (
 "fmt"
 "unsafe"
)
func main() {
 f := &C.struct_Foo{}
 f.a = 5
 f.p = (*C.int)((unsafe.Pointer)(new(int))
 C.plusOne(&f.a)
 fmt.Println(int(f.a))
}

直接指向go run,打印结果为6。可以看出,因为这次调用只传递单个field指针,指向这个field所占用的内存,而这个field也没有嵌套其他指向Go Memory的指针,因此这是符合规范的调用,不会触发panic

传递指向slice或array中的element指针

和传递struct field不同,传递一个指向slice或者array中的element指针时,需要考虑的Go Memory的范围不仅仅是这个element,而是整个array或这个slice背后的underlying array所占用的内存区域,要保证整个区域内不包含任何指向Go Memory的指针。

package main
/*
#include <stdio.h>
void plusOne(int **i) {
 (**i)++;
}
*/
import "C"
import (
 "fmt"
 "unsafe"
)
func main() {
 s1 := make([]*int, 5)
 var a int = 5
 s1[1] = &a
 C.plusOne((**C.int)((unsafe.Pointer)(&s1[0])))
 fmt.Println(s1[0])
}

从以上代码可以看出,传递给C的是slice第一个element的地址,并不包括指向Go Memory的指针,但由于第二个element保存了另外一块Go Memory的地址(&a),当运行go run时,获得报错:panic runtime error: cgo argument has Go pointer to Go pointer

C调用Go

返回指向Go分配的内存的指针

C调用的Go函数不能返回指向Go分配的内存的指针。
package main
// extern int* goAdd(int, int);
//
// int cAdd(int a, int b) {
// int *i = goAdd(a, b);
// return *i;
// }
import "C"
import "fmt"
// export goAdd
func goAdd(a, b C.int) {
 c := a + b
 return &c
}
func main() {
 var a, b int = 5, 6
 i := C.cAdd(C.int(a), C.int(b))
 fmt.Println(int(i))
}

上面代码中,goAdd这个Go函数返回了一个指向Go分配的内存(&c)的指针。运行上述代码,结果如下:panic runtime error: cgo result has Go pointer

在C分配的内存中存储指向Go分配的内存的指针

Go Code不能在C分配的内存中存储指向Go分配的内存的指针。
package main
// #include <stdlib.h>
// extern void goFoo(int**);
//
// void cFoo() {
// int **p = malloc(sizeof(int*));
// goFoo(p);
// }
import "C"
//export goFoo
func goFoo(p **C.int) {
 *p = new(C.int)
}
func main() {
 C.cFoo()
}

针对此例,默认的GODEBUG=cgocheck=1是正常运行的,将GODEBUG=cgocheck=2则会发生报错:fatal error: Go pointer stored into non-Go memory

检测控制

以上规则会在运行时动态检测,可以通过设置GODEBUG环境变量修改检测程度,默认值是GODEBUG=cgocheck=1,可以通过设置为0取消这些检测,也可以通过设置为2来提高检测标准,但这会牺牲运行的效率。

此外,也可以通过使用unsafe包来逃脱这些限制,而且C语言方面也没法使用什么特殊的机制来限制调用Go。尽管如此,如果程序打破了上面的限制,很可能会以一种无法预料的方式调用失败。

小结

cgo中,Go与C的内存应该保持着相对独立,指针之间的传递应该尽量避免嵌套不同内存的指针(如C中保存Go指针)。指针之间传递的规则不是绝对要遵守的,可以通过多种方式忽视检测,但是这往往导致无法预料的结果。


有疑问加站长微信联系(非本文作者)

本文来自:Segmentfault

感谢作者:p了个c

查看原文:cgo的指针传递

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
3357 次点击
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏