Search code examples
gocgo

Go pointer stored into non-Go memory


I've got a newbie CGO question I was wondering if someone could help me with. When running with GODEBUG set to cgocheck=2 my application crashes with the following

write of Go pointer 0xc0003b72c0 to non-Go memory 0x7fefa0016810
fatal error: Go pointer stored into non-Go memory

The code causing the issue is

cArray := C.malloc(C.size_t(len(fd.Faces)) * C.size_t(unsafe.Sizeof(uintptr(0))))
defer C.free(unsafe.Pointer(cArray))
a := (*[1<<30 - 1]*C.struct_Box)(cArray)
for index, value := range fd.GetFaceRectangles() {
    box := &C.struct_Box{
        left: C.int(value.Min.X),
        top: C.int(value.Min.Y),
        right: C.int(value.Max.X),
        bottom: C.int(value.Max.Y),
    }
    a[index] = box
}
cBoxCount := C.int(len(fd.Faces))
ret := C.facerec_compute_multi(rec.ptr, cImgData, cLen,  &a[0], cBoxCount)

Specifically this row:

a[index] = box

I understand the memory for the array is allocated in C using malloc. I'm trying to add C Box to the array before passing it to a C function. Would the fix for this be for me to write a function in C which can receive the array and the items needed to create the struct and I do that part in C instead? I'm trying to minimise the number of calls to C so If I can create the array from Go that would be great. Really struggling with how to pass an array of data through to a function in C safely.


Solution

  • You've allocated C memory to hold many individual pointers. Each individual pointer can be set to a separate value. That's fine as far as it goes, but as you noted, the a[index] = box is a problem, because box itself holds a pointer to a Go-memory C.struct_Box.

    I'm trying to add C Box to the array before passing it to a C function.

    To do that literally, you need a C.malloc call. As written, you'll need one per C.struct_Box instance.

    Would the fix for this be for me to write a function in C which can receive the array and the items needed to create the struct and I do that part in C instead?

    While you could try that, it seems ... suboptimal.

    I'm trying to minimise the number of calls to C so If I can create the array from Go that would be great. Really struggling with how to pass an array of data through to a function in C safely.

    C is never really safe 😀 but I suspect your best bet here is to make use of the way C implements "arrays", which is as a sort of poor-man's slice. (Or, perhaps more accurately, a Go slice is a "C array" done right.) Remember that a slice is implemented in Go as a three-element header:

    • pointer to base of storage area;
    • current length within storage area; and
    • capacity of storage area

    which can then hold some run-time computed number n of items of some type. C's dynamic "arrays" work the same way, only without the safety of a proper slice header.

    What this means is that you can write your C code—provided you have control of this C code—to take a pointer to a base of a storage area, and an int or size_t value that counts the number n of items in that area. The signature of such a function is:

    void f(T *base, size_t n);
    

    where n is the number of items. (Add a second size_t if appropriate, if you want to provide a capacity as well, though if the memory has capacity, and the function modifies it to contain a different number of items, then either the function must return the new number of items—i.e., f would not be void—or n itself has to be provided by reference or something along those lines.)

    In this case, if you can arrange the C code to take a pointer to the first of n struct Box-es (instead of the first of n pointers to struct Box-es), you can allocate that in a single C.malloc call.

    If you can't rework the C code, you're stuck with a minimum of two C.malloc calls. You can, however, allocate all the struct Boxes in one C.malloc again, and make them all adjacent—i.e., a C "array" / poor-man's-slice—and then make each pointer point to the next struct Box:

    cArray := C.malloc(C.size_t(len(fd.Faces)) * C.size_t(unsafe.Sizeof(uintptr(0))))
    defer C.free(unsafe.Pointer(cArray))
    cBoxes := C.malloc(C.size_t(len(fd.Faces)) * C.size_t(unsafe.Sizeof(C.struct_Box)))
    defer C.free(unsafe.Pointer(cBoxes))
    a := (*[1<<30 - 1]*C.struct_Box)(cArray)
    b := (*[1<<30 - 1]C.struct_Box)(cBoxes)
    for index, value := range fd.GetFaceRectangles() {
        b[index] = C.struct_Box{
            left: C.int(value.Min.X),
            top: C.int(value.Min.Y),
            right: C.int(value.Max.X),
            bottom: C.int(value.Max.Y),
        }
        a[index] = &b[index]
    }
    cBoxCount := C.int(len(fd.Faces))
    ret := C.facerec_compute_multi(rec.ptr, cImgData, cLen,  &a[0], cBoxCount)
    

    (I've tried to keep the code structure as similar as possible here, and this is quite untested since I don't have some of the parts.)

    Aside: you have len(fd.Faces) and fd.GetFaceRectangles() and the code here assumes that the number of iterations of a for ... range fd.GetFaceRectangles() is always len(fd.Faces). I'm calling this out because I have made the same assumption, without anything to back it other than your example.