profile picture

Michael Stapelberg

golang: types such as []uint32 and cgo (2012)

published 2012-09-27, last modified 2020-11-21
in tag golang
Edit Icon

There is official documentation on the Go C language interface (or cgo in golang terminology), but the things it covers are relatively simple. I have used cgo recently in a real-world project and I want to share my experiences in this short article, that is, how to use types properly (avoiding the void* equivalent unsafe.Pointer) and how to deal with Go’s data structures such as slices.

A simple example

To make sure we’re on the same page, let’s consider this simple example:

package main
import "fmt"
func main() {
    list := []int{23, 42, 17}
    for idx, val := range list {
        fmt.Printf("index %d: value %d\n", idx, val)
    }
}

The output of that program is:

index 0: value 23
index 1: value 42
index 2: value 17

Multiplying these numbers

Let’s assume that we want to multiply all these numbers by 2. In Go, that’s pretty simple:

package main
import "fmt"

func multiply(input []int) []int {
    // Create an output list with the same size
    output := make([]int, len(input))
    for idx, val := range input {
        output[idx] = val * 2
    }
    return output
}

func main() {
    list := []int{23, 42, 17}
    list = multiply(list)
    for idx, val := range list {
        fmt.Printf("index %d: value %d\n", idx, val)
    }
}

Now let’s see how we would do that in C with cgo. Note that we switch to using uint32 instead of int because that makes the point I’m trying to make easier to convey.

package main
import "fmt"

/*
// Note the -std=gnu99. Using -std=c99 will not work.
#cgo CFLAGS: -std=gnu99
#include <stdint.h>

void cMultiply(int len, uint32_t *input, uint32_t *output) {
    for (int i = 0; i < len; i++) {
        output[i] = input[i] * 2;
    }
}
*/
import "C"

func multiply(input []uint32) []uint32 {
    output := make([]uint32, len(input))
    C.cMultiply(C.int(len(input)),
        (*C.uint32_t)(&input[0]),
	(*C.uint32_t)(&output[0]))
    return output
}

func main() {
    list := []uint32{23, 42, 17}
    list = multiply(list)
    for idx, val := range list {
        fmt.Printf("index %d: value %d\n", idx, val)
    }
}

As you can see, we need to convert the Go types into C types, which can be done by simply type-casting them. Also, we need to manually implement the array calling convention which is normally done by the C compiler: We pass a pointer to the first element.

We have also avoided passing the slice directly to the C code and instead passed the length plus a pointer to the contents. This is a simple way to avoid having to use the internal Go SliceHeader data type.

If you are using C code to speed up some critical routines, you might want to throw in a -O3 in the #cgo CFLAGS pragma.

It is noteworthy that you should avoid calling a lot of cgo-functions, since the function call overhead is much higher than the normal go function call overhead.

Did you like this post? Subscribe to this blog’s RSS feed to not miss any new posts!

I run a blog since 2005, spreading knowledge and experience for over 20 years! :)

If you want to support my work, you can buy me a coffee.

Thank you for your support! ❤️