Rabarar's Blog

A blogging framework for mild hackers.

Cgo and Destructors for Managing Allocated Memory in Go

| Comments

Managing memory allocation when using Cgo

A common challenge when creating Cgo and Golang code that needs to manage dynamically created objects is memory management. How do you create an objet and ensure that the object isn’t leaked. The garbage collector will help manage natural native golang objects that are no longer needed once the scope is exited. But how can we do the same thing when we create dynamic memory objects with Cgo?

Turns out the answer lives at the intersection of runtime.SetFinalizer() and runtime.CG() and a wrapping up the objects in a golang struct.

Here’s an example of a golang program that allocates memory in C, and then frees it with a call to the finalizer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package main

/*

#include <stdlib.h>
#include <stdio.h>

void *foo(void) {
        static int count= 0;
        void *thing = (void *)malloc(sizeof(int));
        *((int *)thing) = count++;
        return thing;
}

*/
import "C"
import (
        "fmt"
        "runtime"
        "sync"
        "time"
        "unsafe"
)

type CFoo struct {
        sync.Mutex
        name     string
        allocCnt int
        memory   unsafe.Pointer
}

func (c *CFoo) alloc(name string) {
        c.name = name
        c.Lock()
        defer c.Unlock()
        c.allocCnt++
        fmt.Printf("alloc[%s]: count = %d\n", c.name, c.allocCnt)
        c.memory = C.foo()
        runtime.SetFinalizer(c, free)
}

func free(c *CFoo) {
        C.free(unsafe.Pointer(c.memory))
        c.Lock()
        defer c.Unlock()
        c.allocCnt--
        fmt.Printf("free[%s]: count = %d\n", c.name, c.allocCnt)
}

func Bar() {
        var c1, c2 CFoo

        c1.alloc("cfoo")
        c2.alloc("cbar")

}

func main() {
        for i := 0; i < 10; i++ {
                Bar()
                time.Sleep(time.Second)
                //runtime.GC()
        }
        runtime.GC()
        time.Sleep(time.Second)
        fmt.Println("done.")
}

So what’s going on here? First we create a struct that will contain our dynmaically allocated memory, CFoo. Inside of CFoo I threw in a Mutex, a name, a allocCnt to allow us to track the allocations and releases of the memory, and lastly memory of type unsafe.Pointer that will point to our dnyamically allocated object. The mutex and the count are soley illustrative and aren’t likely needed in practice.

Our CFoo struct contains a function alloc that will do two important things:

  • First it will allocated the memory via Cgo
  • Next it will register the finalizer so that the memoy will be freed when the object leaves scope.

When we create the memory we assign it to our struct member. The object CFoo will exist until the context is no longer valid. And when it’s invalid, the finalizer will be called when the garbage collector is invoked.

the `free(c *CFoo) function will take our CFoo and call the cgo free() to release the memory.

It’s that simple!

Comments