Go 1.6中cgo的变化记录

2017-01-12 09:58:58来源:开源中国博客作者:Lancerio人点击

第七城市

北京时间2016年2月18日凌晨,在Go 1.5发布 半年后,Go 1.6正式Release 了。与Go 1.5的“惊天巨变”(主要指Go自举)相比,Go 1.6的Change 算是很小的了,当然这也与Go 1.6的dev cycle过于短暂有关。但Go社区对此次发布却甚是重视,其热烈程度甚至超出了Go 1.5。在Dave Cheney的倡导 下,Gophers们在全球各地举行了Go 1.6 Release Party。 Go Core Team也在Reddit上开了一个AMA – Ask Me Anything,RobPike、Russ Cox(Rsc)、Bradfitz等Go大神齐上阵,对广大Gophers们在24hour内的问题有问必答。

言归正传,我们来看看Go 1.6中哪些变化值得我们关注。不过在说变化之前,我们先提一嘴Go 1.6没变的,那就是Go语言的language specification,依旧保持Go 1兼容不变。预计在未来的几个stable release版本中,我们也不会看到Go language specification有任何改动。

一、cgo

cgo的变化在于: 1、定义了在Go code和C code间传递Pointer,即C code与Go garbage collector共存的rules和restriction; 2、在runtime加入了对违规传递的检查,检查的开关和力度由GODEBUG=cgocheck=1[0,2]控制。1是默认;0是关闭检 查;2是更全面彻底但代价更高的检查。

这个Proposal是由Ian Lance Taylor提出的。大致分为两种情况:

(一) Go调用C Code时

Go调用C Code时,Go传递给C Code的Go Pointer所指的Go Memory中不能包含任何指向Go Memory的Pointer。我们分为几种情况来探讨一下:

1、传递一个指向Struct的指针

//cgo1_struct.gopackage 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))}

从cgo1_struct.go代码中可以看到,Go code向C code传递了一个指向Go Memory(Go分配的)指针f,但f指向的Go Memory中有一个指针p指向了另外一处Go Memory: new(int),我们来run一下这段代码:

$go run cgo1_struct.go# command-line-arguments./cgo1_struct.go:12:2: warning: expression result unused [-Wunused-value]panic: runtime error: cgo argument has Go pointer to Go pointergoroutine 1 [running]:panic(0x4068400, 0xc82000a110) /Users/tony/.bin/go16/src/runtime/panic.go:464 +0x3e6main.main() /Users/tony/test/go/go16/cgo/cgo1_struct.go:24 +0xb9exit status 2

代码出现了Panic,并提示:“cgo argument has Go pointer to Go pointer”。我们的代码违背了Cgo Pointer传递规则,即便让f.p指向struct自身内存也是不行的,比如f.p = &f.a。

2、传递一个指向struct field的指针

按照rules中的说明,如果传递的是一个指向struct field的指针,那么”Go Memory”专指这个field所占用的内存,即便struct中有其他field指向其他Go memory也不打紧:

//cgo1_structfield.gopackage 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 cgo1_structfield.go6

3、传递一个指向slice or array中的element的指针

和传递struct field不同,传递一个指向slice or array中的element的指针时,需要考虑的Go Memory的范围不仅仅是这个element,而是整个Array或整个slice背后的underlying array所占用的内存区域,要保证这个区域内不包含指向任意Go Memory的指针。我们来看代码示例:

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

从这个代码中,我们看到我们传递的是slice的第一个element的地址,即&sl[0]。我们并未给sl[0]赋值,但sl[1] 被赋值为另外一块go memory的address(&a),当我们将&sl[0]传递给plusOne时,执行结果如下:

$go run cgo1_sliceelem.gopanic: runtime error: cgo argument has Go pointer to Go pointergoroutine 1 [running]:panic(0x40dbac0, 0xc8200621d0) /Users/tony/.bin/go16/src/runtime/panic.go:464 +0x3e6main.main() /Users/tony/test/go/go16/cgo/cgo1_sliceelem.go:19 +0xe4exit status 2

由于违背规则,因此runtime panic了。

(二) C调用Go Code时

1、C调用的Go函数不能返回指向Go分配的内存的指针

我们看下面例子:

//cgo2_1.go

package main// extern int* goAdd(int, int);//// static int cAdd(int a, int b) {// int *i = goAdd(a, b);// return *i;// }import "C"import "fmt"//export goAddfunc goAdd(a, b C.int) *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)的指针。运行上述代码,结果如下:

$go run cgo2_1.gopanic: runtime error: cgo result has Go pointergoroutine 1 [running]:panic(0x40dba40, 0xc82006e1c0) /Users/tony/.bin/go16/src/runtime/panic.go:464 +0x3e6main._cgoexpwrap_872b2f2e7532_goAdd.func1(0xc820049d98) command-line-arguments/_obj/_cgo_gotypes.go:64 +0x3amain._cgoexpwrap_872b2f2e7532_goAdd(0x600000005, 0xc82006e19c) command-line-arguments/_obj/_cgo_gotypes.go:66 +0x89main._Cfunc_cAdd(0x600000005, 0x0) command-line-arguments/_obj/_cgo_gotypes.go:45 +0x41main.main() /Users/tony/test/go/go16/cgo/cgo2_1.go:20 +0x35exit status 2

2、Go code不能在C分配的内存中存储指向Go分配的内存的指针

下面的例子模拟了这一情况:

//cgo2_2.gopackage main// #include <stdlib.h>// extern void goFoo(int**);//// static void cFoo() {// int **p = malloc(sizeof(int*));// goFoo(p);// }import "C"//export goFoofunc goFoo(p **C.int) { *p = new(C.int)}func main() { C.cFoo()}

不过针对此例,默认的GODEBUG=cgocheck=1偏是无法check出问题。我们将GODEBUG=cgocheck改为=2试试:

$GODEBUG=cgocheck=2 go run cgo2_2.gowrite of Go pointer 0xc82000a0f8 to non-Go memory 0x4300000fatal error: Go pointer stored into non-Go memoryruntime stack:runtime.throw(0x4089800, 0x24) /Users/tony/.bin/go16/src/runtime/panic.go:530 +0x90runtime.cgoCheckWriteBarrier.func1() /Users/tony/.bin/go16/src/runtime/cgocheck.go:44 +0xaeruntime.systemstack(0x7fff5fbff8c0) /Users/tony/.bin/go16/src/runtime/asm_amd64.s:291 +0x79runtime.mstart() /Users/tony/.bin/go16/src/runtime/proc.go:1048... ...goroutine 17 [syscall, locked to thread]:runtime.goexit() /Users/tony/.bin/go16/src/runtime/asm_amd64.s:1998 +0x1exit status 2

果真runtime panic: write of Go pointer 0xc82000a0f8 to non-Go memory 0×4300000

第七城市

相关文章

    无相关信息

最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台