This document provides a detailed explanation of a simple garbage collector implemented in C. The garbage collector uses a combination of macros and the __attribute__((cleanup(...))) GCC extension to automatically manage memory for different data types, including raw pointers, vectors (dynamic arrays), strings, and file descriptors.
The core of the system is the "smart pointer" concept. By using the __attribute__((cleanup(func))) attribute, a variable can be associated with a specific cleanup function that is automatically called when the variable goes out of scope. This ensures that resources (memory, file handles) are released without manual intervention.
The _C(func) macro is a shorthand for this attribute.
Every memory block managed by this garbage collector is preceded by a ptr_header_t struct. This header stores vital metadata about the allocation.
typedef struct s_ptr_header {
unsigned int magic; // Must be 0xDEADBEEF
const char *type;
const char *file;
int refs;
int line;
size_t size;
size_t capacity;
} ptr_header_t;magic: A constant value (0xDEADBEEF) to verify that a pointer is indeed managed by this GC.type: A string representing the data type (e.g.,"char *").file,line: The source file and line number where the pointer was created, for debugging.refs: A reference counter for shared ownership.size: The number of elements in the allocation (for vectors) or bytes.capacity: The total allocated capacity in bytes (for vectors).
The GET_HDR(p) macro is used to retrieve the header from a given user-facing pointer.
These macros are the primary way to interact with the garbage collector.
-
OWN(n, t, e): Creates an owned smart pointer. This is the most common macro.n: The name of the variable.t: The type of the variable.e: The expression that provides the initial pointer (e.g.,malloc(...),strdup(...)).- Cleanup: The associated memory is freed when the variable goes out of scope.
-
SHARE(n, t, p): Creates a shared smart pointer from an existing smart pointerp.- This increments the reference count (
refs) in the pointer's header. - Cleanup: The reference count is decremented. The memory is only freed when the count reaches zero.
- This increments the reference count (
STRING(name, s): A convenient wrapper aroundOWNfor creating a managed string. It usesstrdupinternally.FOPEN(name, p, m): Wraps aFILE *fromfopen.- Cleanup: Automatically calls
fclose.
- Cleanup: Automatically calls
OPEN(n, path, flags): Wraps an integer file descriptor fromopen.- Cleanup: Automatically calls
close.
- Cleanup: Automatically calls
The garbage collector includes a fully-featured, type-safe dynamic array (vector) implementation. A vector created with this system will automatically grow as needed and will be freed when it goes out of scope.
Vectors are created like any other smart pointer, typically by initializing an OWN variable to NULL.
OWN(my_vector, int *, NULL); // Creates an empty vector of integersAPPEND(vec, val, t): Appends a valuevalof typetto the end of the vectorvec. The vector grows automatically if its capacity is exceeded.REMOVE(vec, idx, t): Removes the element atidxof typetfrom the vector.LEN(v): Returns the number of elements currently in the vector.CAP(v): Returns the total capacity of the vector.FOREACH(i, arr): A convenience macro for iterating over a vector from index0toLEN(arr) - 1.MAP(arr, func): Applies a functionfuncto each element of the vector.
These are the underlying functions that power the macro-based API.
void *gc_init(void *raw_ptr, const char *type, char *file, int line): Initializes the metadata header for a new smart pointer.void *gc_share(void *raw_ptr): Handles the logic for creating a shared pointer (increments reference count).void gc_cleanup(void *p): The main cleanup function forOWNpointers. It decrements the reference count and frees the memory if the count is zero.void *gc_append_vec(void *arr, void *item, size_t item_size): The function behind theAPPENDmacro.void gc_remove_vec(void *arr, size_t index, size_t item_size): The function behind theREMOVEmacro.void gc_map_vec(void *arr, int (*func)(int)): The function behind theMAPmacro.void gc_map_string(void *arr, char (*func)(char)): The function behind theSMAPmacro.char *smart_read(FILE *stream): Reads an entire file stream into a new, managed string.gc_f_arr,gc_f_file,gc_close_fd: Specialized cleanup functions for arrays of pointers,FILE*, and file descriptors, respectively.
#include "gc.h"
#include <stdio.h>
int main(void)
{
// 1. Owned string
STRING(message, "Hello from the garbage collector!");
printf("Message: %s\n", message);
// 2. Vector (dynamic array) of integers
OWN(my_vector, int *, NULL); // Initialize as NULL
for (int i = 0; i < 5; ++i) {
APPEND(my_vector, i * 10, int);
}
printf("Vector (len=%zu, cap=%zu): ", LEN(my_vector), CAP(my_vector));
FOREACH(i, my_vector) {
printf("%d ", my_vector[i]);
}
printf("\n");
REMOVE(my_vector, 2, int); // Remove the 3rd element (20)
printf("After removing element at index 2: ");
FOREACH(i, my_vector) {
printf("%d ", my_vector[i]);
}
printf("\n");
// 3. File handling
FOPEN(my_file, "example.txt", "w+");
if (my_file) {
fprintf(my_file, "This is a test file.\n");
rewind(my_file);
STRING(content, smart_read(my_file));
printf("File content: %s", content);
}
// 4. Shared Pointers
OWN(original_ptr, int *, malloc(sizeof(int)));
*original_ptr = 123;
{
SHARE(shared_ptr, int *, original_ptr); // Create a shared reference
printf("Value from shared pointer: %d\n", *shared_ptr);
// 'shared_ptr' goes out of scope, ref count decremented
}
printf("Value from original pointer: %d\n", *original_ptr);
return 0;
}
// All variables created with OWN, STRING, FOPEN, etc. are automatically
// cleaned up here as they go out of scope.