Search docsSearch this language
Using C2Go
Release candidatev0.20260809.0-rc.5

These docs describe the current coordinated release candidate.

Using C2Go

Memory and the Go GC

Decide whether C data belongs to the Go garbage collector or keeps an ordinary C lifetime.

C programs normally decide when memory is released. Go lets the garbage collector decide when an object is no longer reachable. C2Go must know which lifetime a C pointer belongs to; otherwise the Go GC could miss an object that is still in use.

Managed and unmanaged describe how the GC sees pointers; they are not an automatic ownership system. APIs must still define who creates and releases memory and whether a callee retains a pointer.

Start with two questions:

  1. Will the Go GC reclaim this memory, or will code, the operating system, or a native library release it?
  2. Does the memory contain pointers that the Go GC must continue following?

The practical difference

Situation Choose Why
An object created with gc_malloc that survives across Go calls managed The Go GC needs its type and pointer layout
A Go-owned list, tree, or other pointer-bearing object managed Pointer stores need the Go write barrier
An ordinary malloc/free buffer unmanaged C code still controls its lifetime
A structure passed to the OS, a dynamic library, or a third-party C ABI unmanaged It must retain native C memory and ABI semantics
An ordinary function local keep the default Use it only during the call; never retain its address

The current C2Go default is ordinary unmanaged C behavior. Opt into managed memory only when the Go GC genuinely needs to track the data.

Objects managed by the Go GC

This list node stores another node pointer and may survive across several Go/C calls, so it is declared managed:

#include <c2go.h>

struct managed Node {
    struct Node *next;
    int value;
};

struct Node *new_node(int value) {
    struct Node *node = gc_malloc(
        c2go_typeinfo(struct Node), sizeof(*node));
    node->next = 0;
    node->value = value;
    return node;
}

Three pieces work together:

  • managed tells the compiler which fields are GC pointers;
  • c2go_typeinfo(struct Node) supplies the type layout; and
  • gc_malloc creates the typed object on the Go heap.

When assigning node->next, the compiler emits the write barrier required by the Go runtime. The managed marker does not allocate memory and does not automatically move a stack local to the heap.

Keep an ordinary C lifetime

Data passed to the operating system, a dynamic library, or an existing C API normally remains unmanaged:

#include <c2go.h>

struct unmanaged NativeBuffer {
    void *data;
    size_t length;
};

extern int vendor_write(struct NativeBuffer *buffer);

Ordinary malloc, calloc, realloc, and free also retain manual-release semantics. Do not store managed pointers in such raw buffers: the GC does not know their internal field layout.

A declared-only function such as vendor_write is already a native import. It does not need an explicit unmanaged marker.

Use unmanaged at a boundary when making that intent explicit helps readers. It is also the current behavior of otherwise unmarked ordinary C types.

Common mistakes

Retaining the address of a local

void attach(struct Node *node) {
    struct Node local;
    node->next = &local; // local dies when the function returns
}

C2Go does not automatically promote this C local to the heap the way the Go compiler may do for Go source. Source diagnostics only cover some direct forms, so release builds must also run the stack escape audit.

Mixing lifetimes without an explicit boundary

Storing a managed pointer in unmanaged storage, converting it to an integer, or accessing it through an incompatible layout can hide the pointer from the GC. C2Go diagnoses these operations with warnings or errors. Do not route around them through integers or disabled warnings; redesign the boundary or copy the data into a representation with a clear lifetime.

Making pointer fields impossible to locate

A layout containing GC pointers cannot be packed, overlapped, or custom-aligned arbitrarily. Special managed-record layouts produce warnings; unmanaged layouts that cannot be scanned safely are rejected. Inspect the generated Go type and test every target before exposing such a layout publicly.

Rule of thumb

Start with ordinary C behavior. Introduce managed types and gc_malloc only when an object lives on the Go heap or contains pointers that the Go GC must follow. Keep memory used by the OS, dynamic libraries, and native ABIs unmanaged, with an explicit creation and release relationship.

The full attribute, pragma-bitmask, and conversion-diagnostic syntax belongs in the C2Go extensions reference. Next, use GC-aware allocation to build a complete managed object.