cgo in go 1.6

The major change is the definition of rules for sharing Go pointers with C code, to ensure that such C code can coexist with Go’s garbage collector. Briefly, Go and C may share memory allocated by Go when a pointer to that memory is passed to C as part of a cgo call, provided that the memory itself contains no pointers to Go-allocated memory, and provided that C does not retain the pointer after the call returns. These rules are checked by the runtime during program execution: if the runtime detects a violation, it prints a diagnosis and crashes the program. The checks can be disabled by setting the environment variable GODEBUG=cgocheck=0, but note that the vast majority of code identified by the checks is subtly incompatible with garbage collection in one way or another. Disabling the checks will typically only lead to more mysterious failure modes. Fixing the code in question should be strongly preferred over turning off the checks.

Because of those change, we must notice the rules for sharing pointers with C code.

Go code may pass a Go pointer to C provided the Go memory to which it points does not contain any Go pointers.

The C code must preserve this property: it must not store any Go pointers in Go memory, even temporarily. When passing a pointer to a field in a struct, the Go memory in question is the memory occupied by the field, not the entire struct. When passing a pointer to an element in an array or slice, the Go memory in question is the entire array or the entire backing array of the slice.

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)))

    C.plusOne(f)
    fmt.Println(int(f.a))
}
$ go run test.go
panic: runtime error: cgo argument has Go pointer to Go pointer

goroutine 1 [running]:
panic(0x4dc3e0, 0xc82000a280)
    /usr/local/go/src/runtime/panic.go:464 +0x3e6
main.main()
    /go/test.go:24 +0xc1
exit status 2
package 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])
}
$ go run test2.go
panic: runtime error: cgo argument has Go pointer to Go pointer

goroutine 1 [running]:
panic(0x4dc260, 0xc82000a270)
    /usr/local/go/src/runtime/panic.go:464 +0x3e6
main.main()
    /go/test2.go:19 +0xe4
exit status 2

C code may not keep a copy of a Go pointer after the call returns.

A Go function called by C code may not return a Go pointer.

A Go function called by C code may take C pointers as arguments, and it may store non-pointer or C pointer data through those pointers, but it may not store a Go pointer in memory pointed to by a C pointer.

A Go function called by C code may take a Go pointer as an argument, but it must preserve the property that the Go memory to which it points does not contain any Go pointers.

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 goAdd
func 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))
}
$ go run test3.go
panic: runtime error: cgo result has Go pointer

goroutine 1 [running]:
panic(0x4dc100, 0xc82000a260)
    /usr/local/go/src/runtime/panic.go:464 +0x3e6
main._cgoexpwrap_6c682d6da1ed_goAdd.func1(0xc820041d98)
    command-line-arguments/_obj/_cgo_gotypes.go:64 +0x3a
main._cgoexpwrap_6c682d6da1ed_goAdd(0x600000005, 0xc82000a23c)
    command-line-arguments/_obj/_cgo_gotypes.go:66 +0x89
main._Cfunc_cAdd(0x600000005, 0x0)
    command-line-arguments/_obj/_cgo_gotypes.go:45 +0x41
main.main()
    /go/test3.go:20 +0x35
exit status 2

Go code may not store a Go pointer in C memory. C code may store Go pointers in C memory, subject to the rule above: it must stop storing the Go pointer when the C function returns.

These rules are checked dynamically at runtime.

The checking is controlled by the cgocheck setting of the GODEBUG environment variable. The default setting is GODEBUG=cgocheck=1, which implements reasonably cheap dynamic checks. These checks may be disabled entirely using GODEBUG=cgocheck=0. Complete checking of pointer handling, at some cost in run time, is available via GODEBUG=cgocheck=2.

It is possible to defeat this enforcement by using the unsafe package, and ofcourse there is nothing stopping the C code from doing anything it likes. However, programs that break these rules are likely to fail in unexpected and unpredictable ways.

Reference: https://golang.org/cmd/cgo/

comments powered by Disqus