5

I am trying to interface with some C code from Go. Using cgo, this has been relatively straight-forward until I hit this (fairly common) case: needing to pass a pointer to a structure that itself contains a pointer to some data. I cannot seem to figure out how to do this from Go without resorting to putting the creation of the structure into the C code itself, which I'd prefer not to do. Here is a snippet that illustrates the problem:

package main

// typedef struct {
//     int   size;
//     void *data;
// } info;
//
// void test(info *infoPtr) {
//     // Do something here...
// }
import "C"

import "unsafe"

func main() {
    var data uint8 = 5
    info := &C.info{size: C.int(unsafe.Sizeof(data)), data: unsafe.Pointer(&data)}
    C.test(info)
}

While this compiles fine, trying to run it results in:

panic: runtime error: cgo argument has Go pointer to Go pointer

In my case, the data being passed to the C call doesn't persist past the call (i.e. the C code in question digs into the structure, copies what it needs, then returns).

gavv
  • 4,649
  • 1
  • 23
  • 40
Richard Wilkes
  • 309
  • 2
  • 10

1 Answers1

5

See "Passing pointers" section in cgo docs:

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

And also:

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.

If you run the snippet you've provided with:

GODEBUG=cgocheck=0 go run snippet.go

Then there is no panic. However, the correct way to go is to use C.malloc (or obtain a "C pointer" from somewhere else):

package main

// #include <stdlib.h>
// typedef struct {
//     int   size;
//     void *data;
// } info;
//
// void test(info *infoPtr) {
//     // Do something here...
// }
import "C"

import "unsafe"

func main() {
    var data uint8 = 5

    cdata := C.malloc(C.size_t(unsafe.Sizeof(data)))
    *(*C.char)(cdata) = C.char(data)
    defer C.free(cdata)

    info := &C.info{size: C.int(unsafe.Sizeof(data)), data: cdata}
    C.test(info)
}

It works because while regular Go pointers are not allowed, C.malloc returns a "C pointer":

Go pointer means a pointer to memory allocated by Go (such as by using the & operator or calling the predefined new function) and the term C pointer means a pointer to memory allocated by C (such as by a call to C.malloc). Whether a pointer is a Go pointer or a C pointer is a dynamic property determined by how the memory was allocated.

Note that you need to include stdlib.h to use C.free.

gavv
  • 4,649
  • 1
  • 23
  • 40