gocgo

How to call a C function that modifies go memory?


Let's suppose I have a C function with signature

// myclib.h

void modify(double* ptr, int N);

which alters the memory pointed to by the argument pointer ptr.

Is the following code safe in terms of Go's garbage collector and is the runtimer.Pinner needed at all?

package main

// #cgo CFLAGS: -g -Wall
// #include "myclib.h"
import "C"
import (
    "fmt"
    "runtime"
)

func modifyWrapper(v []float64) {
    ptr := (*C.double)(&v[0])
    N := (C.int)(len(v))

    pinner := runtime.Pinner{}
    pinner.Pin(ptr)
    C.modify(ptr, N)
    pinner.Unpin()
}

func main() {
    v := []float64{9.0, 2.0, 1.0, 4.0, 5.0}
    modifyWrapper(v)
}

Solution

  • Cgo Command

    the term Go pointer means a pointer to memory allocated by Go and the term C pointer means a pointer to memory allocated by C. Whether a pointer is a Go pointer or a C pointer is a dynamic property determined by how the memory was allocated; it has nothing to do with the type of the pointer.

    All Go pointers passed to C must point to pinned Go memory. Go pointers passed as function arguments to C functions have the memory they point to implicitly pinned for the duration of the call.


    package main
    
    /*
    #cgo CFLAGS: -g -Wall
    #include <stdio.h>
    void modify(double* ptr, int N) {
        if (!ptr || N <= 0) {
            return;
        }
        printf("modify: %g %d\n", *ptr, N);
        *ptr = 42;
        printf("modify: %g %d\n", *ptr, N);
    }
    */
    import "C"
    
    import (
        "fmt"
        "unsafe"
    )
    
    func modify(v []float64) {
        ptr := (*C.double)(unsafe.SliceData(v))
        N := (C.int)(len(v))
        C.modify(ptr, N)
    }
    
    func main() {
        v := []float64{9.0, 2.0, 1.0, 4.0, 5.0}
        fmt.Println(v)
        modify(v)
        fmt.Println(v)
    }
    

    [9 2 1 4 5]
    modify: 9 5
    modify: 42 5
    [42 2 1 4 5]