Jump to Navigation

GO1.6 cgo使用上的改变

GO1.6 cgo使用上的改变

在新go1.6版本中,针对cgo的指针使用制定了规范,并且体现在了编译器中。
在不规范使用cgo指针的时候,程序将报错退出。

例如,在go1.5中,可以编译返回unsafe.Pointer类型的go导出函数。

  1. //export foo
  2. func foo() unsafe.Pointer {
  3. var retp unsafe.Pointer
  4. return retp
  5. }

这个函数的写法,在go1.5中能够编译通过并且程序运行正常。

但在go1.6中,只能编译,运行却会报错退出了,即这个检测是在运行时才执行的而不是编译时。
运行报错信息为:untime error: cgo result has Go pointer
也就是说默认情况不能够再返回unsafe.Pointer类型了,原因也在cgo文档中说的非常清楚了,影响内存回收,可能导致程序崩溃。

在cgo文档中给出一种解决方式,就是关闭cgo检测,运行时设置GODEBUG=cgocheck=0环境变量。
这样用的好处是代码不需要改变,运行时稍微有些不同。
但是对于非devops来说,不同版本运行不同,有时会有点影响。甚至自己时间长了也会忘记如何启动程序了。

另一种是从代码写法上避免这个问题,虽然代码上不太那么美观,却不用纠结启动时的问题了。
改变后的通用写法,主要是把返回值放在最后一个参数中,这其实是即使在cgo的实现中也经常的一种方法。

  1. //export foo
  2. func foo(retpp*unsafe.Pointer) {
  3. var ret unsafe.Pointer
  4. *retpp = ret
  5. }

下面简单来追踪下这个cgocheck的实现,

_cgo_export.c: foo(void **retpp)
_cgo_gotypes.go: _cgoexp_7ac09bd931f6_foo(retpp*unsafe.Pointer)
_cgo_gotypes.go: _cgoexpwrap_7ac09bd931f6_foo(retpp*unsafe.Pointer)

在_cgo_gotypes.go文件中生成的封装函数,开始有一段代码:

_cgo_gotypes.go:

  1. func _cgoexpwrap_7ac09bd931f6_foo(retpp*unsafe.) {
  2. defer func() {
  3. _cgoCheckResult(r0)
  4. }()
  5. ...
  6. }

这个_cgoCheckResult函数的实现如下,
cgocall.go:591:

  1. func cgoCheckResult(val interface{}) {
  2. if debug.cgocheck == 0 {
  3. return
  4. }
  5.  
  6. ep := (*eface)(unsafe.Pointer(&val))
  7. t := ep._type
  8. cgoCheckArg(t, ep.data, t.kind&kindDirectIface == 0, false, cgoResultFail)
  9. }

在这个函数开始位置检测了debug.cgocheck的值,这是前面提到的cgo文档中的GODEBUG=cgocheck=0设置的值。
需要注意,这个值只能在命令行设置,无法运行时设置,如使用os.Setenv是无效的。

根据这些实现代码来看,实际上go还是允许使用这些的,结果是一个软规范而已。
添加的cgocheck代码也是一种辅助手段,尽量防止出现程序问题吧,依旧无法保证灵活的cgo中C语言的用法。

感觉这个功能的作用不太大。

添加新评论

Plain text

  • 不允许HTML标记。
  • 自动将网址与电子邮件地址转变为链接。
  • 自动断行和分段。
CAPTCHA
This question is for testing whether or not you are a human visitor and to prevent automated spam submissions.
Image CAPTCHA
Enter the characters shown in the image.


Main menu 2

Story | by Dr. Radut