A header-only, conservative tracing garbage collector in C.
- Scans the stack, registers, globals, and heap objects for potential pointers
- Cross-platform (works on Linux, macOS, Windows; GCC, Clang, MSVC)
- Optional multithreading, define
GC_THREADEDbefore the include - Run cleanup code when an object is collected (finalizers)
- Prevent specific objects from being collected (pinning)
- Register pointers that live outside the GC's natural scan region, i.e. explicit roots
- Weak references are nulled automatically after collection
- Objects known to contain no pointers (skips interior scan for leaf allocations)
- Preclude arbitrary memory ranges to the mark scan with explicit segments
- Pause / resume, temporarily suppress automatic GC
- Collection callback hook called after every collection cycle
__attribute__((cleanup))macros for RAII-style root/pin management (GCC/Clang)- Configurable threshold tuned when automatic collection triggers
#include "gc.h"
int main(void) {
GC_INIT();
int *x = GC_NEW(int);
*x = 8;
char *s = GC_STRDUP("hello");
GC_COLLECT(); // force a collection
GC_DESTROY();
}Compile normally, no extra flags needed unless you want threads:
gcc -o prog prog.c # single-threaded
gcc -o prog prog.c -DGC_THREADED -lpthread # multithreaded (POSIX)
gcc -o prog prog.c -DGC_THREADED # multithreaded (Windows)| Function | Description |
|---|---|
gc_init() |
Initialise the global GC state |
gc_state_new(threshold) |
Create a standalone GC state |
gc_state_destroy(g) |
Destroy a state (NULL → global) |
gc_collect(g) |
Run a collection cycle |
| Function | Description |
|---|---|
gc_alloc(sz) |
Allocate sz bytes |
gc_zalloc(sz) |
Allocate and zero sz bytes |
gc_alloc_leaf(sz) |
Allocate a leaf (no interior pointer scan) |
gc_alloc_ex(g, sz, flags, fin, ud) |
Full-control allocation |
gc_realloc(ptr, sz) |
Resize a GC allocation |
gc_strdup(s) |
Duplicate a string (leaf) |
gc_strndup(s, max) |
Duplicate up to max chars (leaf) |
| Function | Description |
|---|---|
gc_pin(ptr) |
Prevent object from being collected |
gc_unpin(ptr) |
Allow collection again |
gc_make_leaf(ptr) |
Mark object as containing no pointers |
gc_set_fin(ptr, fn, ud) |
Attach a finalizer |
| Function | Description |
|---|---|
gc_add_root(ptr) |
Register an explicit GC root |
gc_remove_root(ptr) |
Unregister a root |
gc_add_weak(ptr_to_ptr) |
Register a weak reference |
gc_add_segment(lo, hi) |
Scan an arbitrary memory range |
| Function | Description |
|---|---|
gc_pause() |
Pause automatic collection |
gc_resume() |
Resume automatic collection |
gc_set_threshold(t) |
Set the byte threshold for auto-collect |
gc_set_cb(fn, ud) |
Set a post-collection callback |
gc_allocated() |
Bytes currently tracked |
gc_count() |
Number of live objects |
GC_NEW(T) // allocate sizeof(T), cast to T*
GC_NEW_LEAF(T) // leaf variant
GC_ARRAY(T, n) // allocate n * sizeof(T), cast to T*
GC_ARRAY_LEAF(T, n) // leaf variant
GC_STRDUP(s) // gc_strdup
GC_STRNDUP(s, n) // gc_strndup
GC_REALLOC(p, sz) // gc_realloc
GC_ALLOC(sz) // gc_allocThese use __attribute__((cleanup)) to automatically run cleanup when the variable goes out of scope.
GC_AUTO(T, name, expr) // declare a GC pointer (no special cleanup)
GC_AUTO_ROOT(T, name, expr) // declare + root; unrooted on scope exit
GC_AUTO_PIN(T, name, expr) // declare + pin; unpinned on scope exit
GC_SCOPED_PAUSE() // pause GC; auto-resumes on scope exit
GC_WEAK(T, name, expr) // declare a weak referenceGC_INIT() // gc_init()
GC_DESTROY() // gc_state_destroy(NULL)
GC_COLLECT() // gc_collect(NULL)
GC_PAUSE() // gc_pause()
GC_RESUME() // gc_resume()
GC_THRESH(t) // gc_set_threshold(t)
GC_ROOT(p) // gc_add_root
GC_UNROOT(p) // gc_remove_root
GC_PIN(p) // gc_pin
GC_UNPIN(p) // gc_unpin
GC_SETFIN(p,f,u) // gc_set_fin
GC_SEG(lo,hi) // gc_add_segment
GC_BYTES() // gc_allocated()
GC_OBJECTS() // gc_count()
GC_STAT() // print diagnostics to stderrDefine GC_THREADED before including the header. On POSIX platforms link with -lpthread. On Windows no extra flags are needed.
#define GC_THREADED
#include "gc.h"Use gc_thread to spawn threads that register with the collector:
// POSIX
pthread_t t;
gc_thread(&t, my_func, my_arg);
pthread_join(t, NULL);
// Windows
HANDLE h = gc_thread(my_func, my_arg);
WaitForSingleObject(h, INFINITE);The GC mutex guards all allocation and collection operations. Each thread's stack is not scanned automatically in the multi-threaded case, use GC_ROOT / GC_AUTO_ROOT for pointers that need to survive across thread boundaries, or just keep them in global/static variables.
static void cleanup(void *ptr, void *ud) {
my_resource *r = ptr;
fclose(r->file);
}
my_resource *r = GC_NEW(my_resource);
r->file = fopen("data.bin", "rb");
GC_SETFIN(r, cleanup, NULL);Finalizers run synchronously inside gc_collect before the object is freed.
obj *strong = GC_NEW(obj);
GC_WEAK(obj, w, strong);
strong = NULL; // drop the strong reference
GC_COLLECT();
if (w) { /* still alive */ }
else { /* collected, w is now NULL */ }Weak references are nulled at the end of each collection cycle.
gc is a stop-the-world conservative mark-and-sweep collector.
Mark phase:
- Capture CPU registers (so stack values spilled there are still scanned)
- Scan the C call stack from the current stack pointer to the stack base recorded at
gc_init()time - Scan global/static data segments (
.data,.bss) - Scan any explicitly added memory segments
- Scan all explicit roots
- Recursively scan the interior of every reachable object (unless it is a leaf)
Sweep phase:
Traverse the intrusive linked list of all allocated headers. Any object not marked (and not pinned) is freed. Finalizers run before free.
- Not compacting, objects don't move.
- Any integer that looks like a pointer will keep the target alive, which is a deliberate tradeoff of conservative GC.
- Stack scanning is single-threaded. In a multi-threaded program, only the stack of the thread calling
gc_collectis scanned automatically.
Apache v2.0 License