Skip to content

abdimoallim/gc

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

gc

A header-only, conservative tracing garbage collector in C.

Features

  • 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_THREADED before 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

Quick overview

#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)

API

Lifecycle

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

Allocation

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)

Object control

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

Roots & segments

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

Control

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

Macros

Allocation

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_alloc

RAII / cleanup (GCC and Clang only)

These 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 reference

Control

GC_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 stderr

Multithreading

Define 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.

Finalizers

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.

Weak references

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.

Internals

gc is a stop-the-world conservative mark-and-sweep collector.

Mark phase:

  1. Capture CPU registers (so stack values spilled there are still scanned)
  2. Scan the C call stack from the current stack pointer to the stack base recorded at gc_init() time
  3. Scan global/static data segments (.data, .bss)
  4. Scan any explicitly added memory segments
  5. Scan all explicit roots
  6. 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.

Limitations

  • 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_collect is scanned automatically.

License

Apache v2.0 License

About

A header-only, conservative tracing garbage collector in C

Resources

License

Stars

Watchers

Forks

Languages