Collection of C99 single-header libraries (also compiles clean under C89, C11, C17, C23).
Credits to Ameer for suggesting the name.
To use any header, define its implementation macro before including:
- Define
*_IMPLin exactly one translation unit. - Include the header in all units that need the declarations.
**C89 compatibility**: All headers compile under -std=c89 -Wall -Wextra -Werror.
Generic macros (powered by C11’s _Generic) are used by default; for pre-C11 compilers,
each documentation section lists the suffixed direct-call fallbacks (_cstr, _char, etc.).
All of header files are in headers/ directory
- my_assert.h – custom assert macro
- my_commons.h – panic & unreachable utilities
- my_termcolor.h – ANSI terminal color codes
- my_string.h – mutable string view (trim, equality, starts/ends_with)
- my_string_view.h – non-owning immutable string view (slice over const char *)
- my_string_builder.h – dynamic string builder (heap allocated, push, format)
- my_array.h – dynamic array (struct-based, with allocator)
- my_thin_array.h – dynamic thin array (pointer-based)
- my_managed_array.h – dynamic array using the
Allocatorinterface - my_error.h – Rust-style
Resulttype macros - my_flags.h – command-line argument flag parsing
- my_hashtable.h – generic hash table with Robin Hood hashing
- my_allocator.h – abstract allocator interface
- my_c_allocator.h – libc (malloc/free) allocator adapter
- my_arena_allocator.h – arena (bump) allocator
- my_temporary_allocator.h – temporary bump allocator (global buffer)
- my_stream.h – stream I/O with custom format specifiers
- utf8.h – UTF-8 decoding and iteration
Custom assert macro that prints file, line, and the expression on failure, then calls abort().
#include "my_assert.h"
int x = 42;
assert(x == 42); // passes
assert(x == 0); // prints "file.c:12: Assertion Failed: x == 0" and abortsMe abuse macro. me happy.
defer_switch(...)- runs some code after a matching case (poors mandefer)BLOCK- Allows for earlybreak.forange(n_, start_, ...)- Iterate fromstart_, incrementingn_by 1, untiln_<...is false. C89 variant (forange_c89(n_, start_, end_)) requires pre-declaredsize_t n_;.leach(T, name, list)– iterate a linked list (walks->nextpointer). C89 variant (leach_c89(T, name, list)) requires pre-declaredT *name;.iarreach(index, array)– iterate an array struct by index (old style). C89 variant (iarreach_c89(index, array)) requires pre-declaredsize_t index;.arreach(type, var, array)– iterate an array struct by value (new pointer-based style, provides a copy of each element). C89 variant (arreach_c89(type, var, array)) requires pre-declaredtype var;andtype *var##_ptr_, *var##_end_;.
#include <stdio.h>
// defer switch
defer_switch (0) {
printf( "world\n" );
break;
case 0:
printf( "hello " );
continue;
case 1:
printf( "goodbye " );
continue;
}
defer_switch ( 1 ) {
printf( "world\n" );
break;
case 0:
printf( "hello " );
continue;
case 1:
printf( "goodbye " );
continue;
}
// forange
forange (i, 0, 10) {
printf("%zu\n", i);
}
// leach — linked list iteration
struct Node { int value; struct Node *next; };
struct Node third = { 3, NULL };
struct Node second = { 2, &third };
struct Node first = { 1, &second };
leach(struct Node, n, &first) {
printf("node: %d\n", n->value);
}
// Output: 1, 2, 3
// iarreach — indexed iteration over an array struct
struct Ints { size_t cap, len; int *items; };
struct Ints arr = { .items = (int[]){10, 20, 30}, .len = 3 };
iarreach(i, arr) {
printf("arr[%zu] = %d\n", i, arr.items[i]);
}
// arreach — pointer-based value iteration
arreach(int, v, arr) {
printf("v = %d\n", v);
}Utility macros and functions for common patterns.
unused(x)– suppress unused-variable warnings.panic(msg)– printsfile:line: Error: Panic: msg.and exits with code 69.unreachable()– printsfile:line: Error: reached an unreachable.and exits with code 1.unimplemented()– printsfile:line: Error: this is not implemented yet.and exits.
#define MY_COMMONS_IMPLEMENTATION
#include "my_commons.h"
// panic / unreachable / unimplemented
int demo(void) {
unimplemented();
}ANSI escape code macros for terminal color output.
| Macro | Effect |
|---|---|
ANSI_CODE_RESET | Reset all attributes |
ANSI_CODE_BOLD | Bold / bright foreground |
ANSI_CODE_DARK | Dim / dark foreground |
ANSI_CODE_UNDERLINE | Underline text |
ANSI_CODE_BLINK | Blinking text |
ANSI_CODE_REVERSE | Reverse video (swap fg/bg) |
ANSI_CODE_CONCEALED | Concealed / hidden text |
ANSI_CODE_GRAY | Gray foreground |
ANSI_CODE_GREY | Grey foreground (alias for GRAY) |
ANSI_CODE_RED | Red foreground |
ANSI_CODE_GREEN | Green foreground |
ANSI_CODE_YELLOW | Yellow foreground |
ANSI_CODE_BLUE | Blue foreground |
ANSI_CODE_MAGENTA | Magenta foreground |
ANSI_CODE_CYAN | Cyan foreground |
ANSI_CODE_WHITE | White foreground |
ANSI_CODE_BG_GRAY | Gray background |
ANSI_CODE_BG_GREY | Grey background (alias for BG_GRAY) |
ANSI_CODE_BG_RED | Red background |
ANSI_CODE_BG_GREEN | Green background |
ANSI_CODE_BG_YELLOW | Yellow background |
ANSI_CODE_BG_BLUE | Blue background |
ANSI_CODE_BG_MAGENTA | Magenta background |
ANSI_CODE_BG_CYAN | Cyan background |
ANSI_CODE_BG_WHITE | White background |
#include "my_termcolor.h"
printf(ANSI_CODE_RED "Error" ANSI_CODE_RESET ": something broke\n");
printf(ANSI_CODE_GREEN "%-10s" ANSI_CODE_RESET " %d\n", "pass:", 42);A mutable string view supporting common operations (trim, equality, starts/ends_with).
Define MY_STRING_IMPL before include in exactly one translation unit.
struct String {
char *data; // null-terminated buffer
size_t len; // length (not counting null)
};| Function / Macro | Description |
|---|---|
string_from_chars(chs) | Create a String referencing existing memory (no alloc). |
string_as_cstr(self) | Deprecated — use .data directly instead. |
string_eq(self, target) | Generic equality: cstr or String. |
string_starts_with(self, other) | Generic starts-with: char, cstr, or String. |
string_ends_with(self, other) | Generic ends-with: char, cstr, or String. |
string_trim(self) | Trim whitespace from both ends (in-place). |
string_ltrim(self) | Trim leading whitespace (in-place). |
string_rtrim(self) | Trim trailing whitespace (in-place). |
The generic macros use C11 _Generic. For pre-C11, call the _cstr, _char, _string suffixed functions directly.
#define MY_STRING_IMPL
#include "my_string.h"
int main(void) {
struct String view = string_from_chars(" hello world ");
string_trim(&view);
assert(string_eq(&view, "hello world"));
assert(string_starts_with(&view, "hello"));
assert(string_ends_with(&view, "world"));
printf("%s\n", view.data); // hello world
}A non-owning, immutable string view (slice over const char *). All operations return new views — the original data is never modified.
Define MY_STRING_VIEW_IMPL before include in exactly one translation unit.
typedef struct string_view_t {
const char *data;
size_t len;
} string_view_t;| Function / Macro | Description |
|---|---|
sv_from_chars(chs) | Create a view from a null-terminated C string. |
sv_from_parts(data, len) | Create a view from a data pointer and length. |
sv_eq(self, other) | Generic equality: const char * or string_view_t. |
sv_starts_with(self, other) | Generic starts-with: char, const char *, or string_view_t. |
sv_ends_with(self, other) | Generic ends-with: char, const char *, or string_view_t. |
sv_substr(self, start, end) | Extract a substring view [start, end) (clamped). |
sv_trim(self) | Trim leading and trailing whitespace. |
sv_ltrim(self) | Trim leading whitespace. |
sv_rtrim(self) | Trim trailing whitespace. |
The generic macros use C11 _Generic. For pre-C11, call the _char, _cstr, _view suffixed functions directly.
#define MY_STRING_VIEW_IMPL
#include "my_string_view.h"
int main(void) {
string_view_t v = sv_from_chars(" hello world ");
ASSERT(sv_eq_cstr(sv_trim(v), "hello world"));
string_view_t w = sv_substr(v, 2, 12);
ASSERT(sv_starts_with(w, "hello"));
ASSERT(sv_ends_with(w, "world"));
ASSERT(sv_eq(w, sv_from_parts("hello world", 11)));
printf("%.*s\n", (int)w.len, w.data); // hello world
}A dynamic, heap-allocated, null-terminated string builder.
Define MY_STRING_BUILDER_IMPL before include in exactly one translation unit.
Configure GROW_FACTOR before inclusion to override the default (2).
struct string_builder {
char *data; // null-terminated buffer
size_t len; // length (not counting null)
size_t cap; // allocated capacity (not counting null)
};| Function / Macro | Description |
|---|---|
sb(allocator, cstr) | Convenience macro, same as sb_from_chars_copy(allocator, cstr) |
sb_new(allocator, cap) | Allocate empty string builder with given capacity. |
sb_from_chars_copy(allocator, chs) | Allocate a copy of a C string. |
sb_format(allocator, fmt, ...) | sprintf-style formatting into a new heap-allocated builder. |
sb_delete(self) | Free memory and zero out the struct. |
sb_build(self) | Shrink buffer to fit content and return a String (non-owning view). |
sb_build_view(self) | Shrink buffer to fit content and return a string_view_t. |
sb_reserve(self, new_cap) | Ensure at least new_cap capacity. |
sb_resize(self, new_size) | Set length (grows if needed, zero-fills). |
sb_push(self, target) | Generic push: char, cstr, or another string_builder_t. |
sb_pushf(self, fmt, ...) | Append formatted text. |
sb_set_len(self, len) | Set length directly (unsafe — prefer resize). |
The generic macros use C11 _Generic. For pre-C11, call the _char, _cstr, _string suffixed functions directly.
#define MY_STRING_BUILDER_IMPL
#include "my_string_builder.h"
int main(void) {
// Create from format
struct string_builder b = sb_format("Hello %s! Score: %d", "World", 99);
printf("%s\n", b.data); // Hello World! Score: 99
// Push operations
sb_push(&b, " And more.");
sb_pushf(&b, " Pi = %.2f", 3.14);
// b.data = "Hello World! Score: 99 And more. Pi = 3.14"
sb_delete(&b);
}This requires
my_allocator.hto be in the include path
A dynamic array implemented as macros over a struct with cap, len, items fields.
Uses allocator-aware macros (arrpush, arrfree, etc.) that take an allocator as the first argument. Configure INITIAL_CAP and GROW_FACTOR before including.
| Macro | Description |
|---|---|
arrpush(allocator, array, item) | Append an item. |
arrfree(allocator, array) | Free memory and zero len/cap. |
arrgrow(allocator, array) | Grow capacity by GROW_FACTOR. |
arrinsert(allocator, array, item, pos) | Insert item at position (shifts right). |
arrpop(array) | Decrement length (no memory free). |
arrreserve(allocator, array, min_cap) | Ensure at least min_cap capacity. |
arrsize(array) | Total byte size of the allocation. |
marrlen(array) | Get the current length. |
marrcap(array) | Get the current capacity. |
marrget(array, index) | Get the element at index. |
#include "my_array.h"
#include "my_c_allocator.h"
#define MY_C_ALLOCATOR_IMPL
#include "my_c_allocator.h"
struct Ints {
size_t cap;
size_t len;
int *items;
};
int main(void) {
struct Allocator alloc = get_c_allocator();
struct Ints nums = {0};
arrpush(alloc, nums, 10);
arrpush(alloc, nums, 20);
arrpush(alloc, nums, 30);
for (size_t i = 0; i < nums.len; i++) {
printf("nums[%zu] = %d\n", i, nums.items[i]);
}
arrfree(alloc, nums);
}A dynamic array where the metadata (length, capacity) is stored in a header before the pointer. The user interacts with a plain typed pointer.
Define MY_ARRAY_IMPL before include in exactly one TU.
| Macro / Function | Description |
|---|---|
thinarrinit(T) | Allocate a new thin array of type T. |
thinarrfree(arr) | Free the array. |
thinarrlen(arr) | Get length. |
thinarrcap(arr) | Get capacity. |
thinarrpush(arr, val) | Append a value. |
thinarrpop(arr) | Pop and return the last value. |
thinarrsetlen(arr, new_len) | Set length (unsafe). |
thinarrreserve(arr, new_cap) | Reserve capacity. |
thinarrheader(arr) | Get the internal metadata header pointer. |
#define MY_ARRAY_IMPL
#include "my_thin_array.h"
int main(void) {
int *xs = thinarrinit(int);
for (int i = 0; i < 10; i++) {
thinarrpush(xs, i * i);
}
for (size_t i = 0; i < thinarrlen(xs); i++) {
printf("xs[%zu] = %d\n", i, xs[i]);
}
// Use as a normal pointer
assert(xs[0] == 0);
assert(xs[9] == 81);
thinarrfree(xs);
}This requires
my_allocator.hto be in the include path
Like my_array.h but uses the struct Allocator interface instead of bare function macros. The struct must have an .allocator field of type struct Allocator.
| Macro | Description |
|---|---|
marrinit(T, allocator) | Initialize an array struct literal. |
marrpush(array, item) | Append an item. |
marrfree(array) | Free memory. |
marrgrow(array) | Grow by GROW_FACTOR. |
marrinsert(array, item, pos) | Insert at position. |
marrpop(array) | Pop last item. |
marrreserve(array, min_cap) | Reserve at least min_cap. |
arreach(array, index) | Iterate over elements. |
arrprint(array, fmt) | Print all elements with given format. |
arrlen(array) / arrcap(array) / arrget(array, index) | Accessors. |
#include "my_managed_array.h"
#include "my_c_allocator.h"
#define MY_C_ALLOCATOR_IMPL
#include "my_c_allocator.h"
struct Floats {
struct Allocator allocator;
size_t cap;
size_t len;
float *items;
};
int main(void) {
struct Floats arr = marrinit(struct Floats, get_c_allocator());
marrpush(arr, 1.5f);
marrpush(arr, 2.7f);
marrpush(arr, 3.14f);
arreach(arr, i) {
printf("arr[%zu] = %.2f\n", i, arr.items[i]);
}
marrfree(arr);
}This requires
my_commons.hto be in the include path
Rust-style Result(T, E) type macros. Core macros work in C89+; advanced macros (propagate, try, catch) require auto type inference (C23 or GNU extension).
| Macro / Type | Description |
|---|---|
Result(T, E) | Create a Result type with Ok type T and Err type E. |
Ok(ResultType, value) | Construct an Ok result. |
Err(ResultType, value) | Construct an Err result. |
is_ok(result) | Check if a Result is Ok. |
is_err(result) | Check if a Result is Err. |
unwrap(result) | Unwrap the Ok value (aborts on Err). |
unwrap_err(result) | Unwrap the Err value (aborts on Ok). |
expect(result, msg) | Unwrap Ok, printing msg and aborting on Err. |
unwrap_or(result, default) | Unwrap Ok or return a default value on Err. |
propagate(expr) | Return early if Err (requires auto / C23). |
try(name, expr) | Bind Ok value, return early on Err (auto / C23). |
catch(name, expr) | Capture Err value, skip Ok (auto / C23). |
#include "my_error.h"
#include <stdio.h>
typedef Result(int, const char*) IntResult;
static IntResult divide(int a, int b) {
if (b == 0) return Err(IntResult, "division by zero");
return Ok(IntResult, a / b);
}
// Functions returning Result can be chained with try.
// Requires C23 for the auto keyword.
static IntResult compute(int a, int b, int c) {
// try — propagate errors automatically
try (x_val, divide(a, b)) { // unwrap Ok or return Err early
return Ok(IntResult, x_val + c);
}
// without this the compiler will falsely warn you
return (IntResult){0};
}
int main(void) {
IntResult r = compute(10, 2, 5);
if (is_ok(r)) {
printf("compute(10, 2, 5) = %d\n", unwrap(r)); // 10/2 + 5 = 10
}
IntResult r2 = compute(10, 0, 5);
if (is_err(r2)) {
printf("compute(10, 0, 5) = %s\n", unwrap_err(r2)); // "division by zero"
}
// catch — handle the error branch
IntResult r3 = divide(10, 0);
catch (err, r3) {
printf("caught error: %s\n", err);
}
}General-purpose command-line argument flag parsing library. Define flags of type boolean, integer, size_t, or string, then parse argv and let it shuffle non-flag arguments back so you can use them afterwards.
Define MY_FLAGS_IMPL before include in exactly one translation unit.
Define MY_FLAG_DEF to static or inline before inclusion for internal linkage.
| Type | Enum | Description |
|---|---|---|
FLAG_NONE | flag_type_t | Sentinel / unset |
FLAG_SIZE_T | flag_type_t | Parses value with strtoul, stores in size_t |
FLAG_INT | flag_type_t | Parses value with atoi, stores in int |
FLAG_STRING | flag_type_t | Stores pointer to argv string in char * |
FLAG_BOOL | flag_type_t | Sets destination int to 1 (no value consumed) |
flag_call_t | function pointer | void (*)(void *dest, const char *value) — custom callback invoked when the flag is matched |
| Function | Description |
|---|---|
def_flag(dest, type, long_name, short_name, description) | Register a flag. long_name (e.g. "output") gives --output, short_name (e.g. "o") gives -o. Either can be NULL, but not both. description is used by print_help_flag. |
def_flag_call(callback, dest, type, long_name, short_name, description) | Like def_flag but calls callback(dest, value) when the flag is encountered during parsing, instead of using the default type-based handler. |
parse_flag(&argc, &argv) | Parse argv, setting destinations for recognised flags and compacting remaining (non-flag) arguments to the front of argv; updates argc. Supports --flag=value and --flag:value syntax. |
print_help_flag(stream) | Print a formatted help listing of all defined flags to stream. |
#define MY_FLAGS_IMPL
#include "my_flags.h"
#include <stdio.h>
int main(int argc, char **argv) {
int boolean = 0;
int integer = 0;
char *string = NULL;
def_flag(&boolean, FLAG_BOOL, "bool", "b", "Boolean flag");
def_flag(&integer, FLAG_INT, "int", "i", "Integer flag");
def_flag(&string, FLAG_STRING, "string", "s", "String flag");
if (argc < 2) {
fprintf(stderr, "%s <options>\n", argv[0]);
print_help_flag(stderr);
return 0;
}
parse_flag(&argc, &argv);
printf("bool=%d int=%d string=%s\n", boolean, integer, string);
for (int i = 0; i < argc; i++) {
printf("non-flag[%d] = %s\n", i, argv[i]);
}
}#define MY_FLAGS_IMPL
#include "my_flags.h"
#include <stdio.h>
#include <stdlib.h>
static void on_verbose(void *dest, const char *value) {
(void)value;
int *count = dest;
*count += 1;
}
int main(int argc, char **argv) {
int verbose_count = 0;
// count how many times -v / --verbose is passed
def_flag_call(on_verbose, &verbose_count, FLAG_BOOL,
"verbose", "v", "Increase verbosity");
parse_flag(&argc, &argv);
printf("verbose level: %d\n", verbose_count);
}This requires
my_allocator.hto be in the include path
A generic open-addressing hash table with Robin Hood hashing and backward-shift deletion.
Items are stored directly in hash slots (no sparse index indirection). Supports keys of any type with runtime hash and equality function pointers.
Define MY_HASHTABLE_IMPL in exactly one TU.
The hash table struct must have .items, .len, .cap fields.
| Macro | Default | Description |
|---|---|---|
HM_KEY_OFFSET | 0 (first member) | Byte offset of the key for default hash/eq. |
MY_HASHTABLE_CAPACITY | 16 | Initial capacity. |
HM_LOAD_FACTOR_NUM/DEN | 7/10 | Grow when len*DEN >= cap*NUM. |
| Type | Description |
|---|---|
hash_fn_t | size_t (*)(const void *item) |
eq_fn_t | int (*)(const void *a, const void *b) |
| Macro | Description |
|---|---|
hmcreate(allocator, map) | Initialize with default hash/eq (string keys). |
hminit(allocator, map, hash_fn, eq_fn) | Initialize with custom hash/eq (NULL for default). |
hmput(allocator, map, ...) | Insert or replace an item (compound literal). |
hmget(map, key_item) | Pointer to stored item, or NULL. |
hmdel(map, key_item) | Delete item by key. |
hmfree(allocator, map) | Free entire hash table and zero the struct. |
hmcap(map) | Current capacity (number of slots). |
hmlen(map) | Number of occupied slots. |
hmforeach(map, i) | Iterate over occupied slots (i is size_t). |
hmoccupied(map, i) | Check if slot i is occupied. |
hmget / hmdel take a pointer to an item with only the key field set (other fields are ignored).
hmfree only frees the hash table’s internal memory — any heap-allocated data within items (e.g. string keys) must be freed separately.
#define MY_HASHTABLE_IMPL
#include "my_hashtable.h"
#include "my_c_allocator.h"
#define MY_C_ALLOCATOR_IMPL
#include "my_c_allocator.h"
struct Person {
char *name;
int age;
};
struct People {
struct Person *items;
size_t len;
size_t cap;
};
int main(void) {
struct Allocator alloc = get_c_allocator();
struct People people = {0};
hmcreate(alloc, people); // optional — hmput auto-creates
hmput(alloc, people, (&(struct Person){ "Alice", 30 }));
hmput(alloc, people, (&(struct Person){ "Bob", 25 }));
struct Person *p = hmget(people, (&(struct Person){ .name = "Bob" }));
if (p) printf("Bob is %d\n", p->age);
hmdel(people, (&(struct Person){ .name = "Alice" }));
size_t i;
hmforeach(people, i)
printf("%s: %d\n", people.items[i].name, people.items[i].age);
hmfree(alloc, people);
}#include "my_hashtable.h"
struct Entry { int key; double val; };
struct Map { struct Entry *items; size_t len; size_t cap; };
static size_t hash_int(const void *item) {
int key = *(const int *)item;
return (size_t)(key * 2654435761U); // knuth multiplicative
}
static int eq_int(const void *a, const void *b) {
return *(const int *)a == *(const int *)b;
}
int main(void) {
struct Allocator alloc = get_c_allocator();
struct Map m = {0};
hminit(alloc, m, hash_int, eq_int);
hmput(alloc, m, (&(struct Entry){ 42, 3.14 }));
struct Entry *e = hmget(m, (&(struct Entry){ .key = 42 }));
if (e) printf("val = %f\n", e->val);
hmfree(alloc, m);
}This requires
my_assert.hto be in the include path
Abstract allocator interface (vtable pattern). All other allocators conform to this interface.
struct Allocator {
struct AllocatorInterface *vtable;
void *data;
};
struct AllocatorInterface {
void *(*allocate)(void *self, size_t alignment, size_t size);
void *(*reallocate)(void *self, size_t old_size, void *ptr,
size_t alignment, size_t new_size);
void (*free)(void *self, size_t size, void *ptr);
};| Macro | Description |
|---|---|
alloc(allocator, size) | Allocate with default alignment. |
align_alloc(allocator, align, size) | Allocate with given alignment. |
create(allocator, T, ...) | Allocate and initialize a value. |
destroy(allocator, ptr) | Free (size=0 variant). |
recreate(allocator, new_size, ptr) | Reallocate with default alignment. |
xdestroy(allocator, size, ptr) | Free with explicit size. |
| =xrecreate(allocator, old_size, new_size, ptr)= | Reallocate with explicit old size. |
GET_ALIGNMENT(T) | Get the alignment of a type. |
MY_DEFAULT_ALIGNMENT | Default alignment constant. |
CONCAT(a, b) | Token-paste a and b (expands macros first). |
CONCAT_RAW(a, b) | Token-paste a and b (literal). |
| Macro | Equivalent to |
|---|---|
new(T,...) | create(default_allocator_, T, ...) |
make(size) | alloc(default_allocator_, size) |
aligned_make(align, size) | align_alloc(default_allocator_, align, size) |
delete(ptr) | destroy(default_allocator_, ptr) |
renew(new_size, ptr) | recreate(default_allocator_, new_size, ptr) |
| Function | Description |
|---|---|
allocator_new(data, vtable) | Construct an Allocator. |
set_default_allocator(allocator) | Set the global default allocator. |
get_default_allocator() | Get the global default allocator. |
allocator_alloc(allocator, alignment, size) | Low-level allocate. |
allocator_alloc_with_value(allocator, alignment, size, value) | Allocate and copy a value. |
allocator_realloc(allocator, old_size, ptr, alignment, new_size) | Low-level reallocate. |
allocator_free(allocator, size, ptr) | Low-level free. |
clone_memory(allocator, ptr, len) | Heap-allocate a copy of memory. |
clone_string(allocator, str) | Heap-allocate a copy of a C string. |
nclone_string(allocator, str, len) | Copy at most len chars. |
#define MY_ALLOCATOR_IMPL
#include "my_allocator.h"
int main(void) {
// Uses the default allocator (initially NULL — must be set up)
// For the default to work, point it at a real allocator first:
// set_default_allocator(get_c_allocator());
int *p = new(int, 42);
printf("%d\n", *p); // 42
delete(p);
}This requires
my_allocator.hto be in the include path
Wraps libc’s malloc=/=realloc=/=free into the Allocator interface.
Define MY_C_ALLOCATOR_IMPL before include.
| Function | Description |
|---|---|
get_c_allocator(void) | Returns an Allocator backed by C’s malloc=/=free. |
#define MY_ALLOCATOR_IMPL
#define MY_C_ALLOCATOR_IMPL
#include "my_allocator.h"
#include "my_c_allocator.h"
int main(void) {
struct Allocator c_alloc = get_c_allocator();
int *nums = alloc(c_alloc, 5 * sizeof(int));
nums[0] = 10;
nums[1] = 20;
destroy(c_alloc, nums);
}This requires
my_allocator.hto be in the include path
Bump/arena allocator that allocates from large blocks. Frees are no-ops; the entire arena is freed at once.
Define MY_ARENA_ALLOCATOR_IMPL before include.
Configure ARENA_REGION_DEFAULT_CAPACITY before include (default 8*1024).
| Function / Macro | Description |
|---|---|
arena_new(child_allocator) | Create arena backed by given allocator. |
arena_new_default() | Create arena backed by the default allocator. |
arena_free(self) | Free all arena blocks. |
arena_get_allocator(arena) | Get an Allocator that allocates from the arena. |
arena_print(out, arena) | Print block usage stats. |
ARENA_REGION_DEFAULT_CAPACITY | Default block size (8192, overridable). |
#define MY_ALLOCATOR_IMPL
#define MY_C_ALLOCATOR_IMPL
#define MY_ARENA_ALLOCATOR_IMPL
#include "my_allocator.h"
#include "my_c_allocator.h"
#include "my_arena_allocator.h"
int main(void) {
struct Arena arena = arena_new_default();
struct Allocator a = arena_get_allocator(&arena);
int *xs = alloc(a, 100 * sizeof(int));
for (int i = 0; i < 100; i++) xs[i] = i;
char *msg = alloc(a, 64);
strcpy(msg, "arena example");
arena_free(&arena); // frees everything at once
}This requires
my_allocator.hto be in the include path
A bump allocator using a global fixed-size buffer (default 2048 bytes, or heap-allocated if setup_temporary_allocator is called). Resets are fast — just set used back to 0.
Define MY_TEMPORARY_ALLOCATOR_IMPL before include.
| Function / Macro | Description |
|---|---|
setup_temporary_allocator(size) | Replace internal buffer with a larger heap allocation. |
free_temporary_allocator() | Free the heap buffer, revert to static buffer. |
get_temporary_allocator() | Get an Allocator into the temporary region. |
reset_temporary_allocator() | Reset used count to 0 (fast clear). |
#define MY_ALLOCATOR_IMPL
#define MY_TEMPORARY_ALLOCATOR_IMPL
#include "my_allocator.h"
#include "my_temporary_allocator.h"
int main(void) {
struct Allocator tmp = get_temporary_allocator();
int *scratch = alloc(tmp, 128 * sizeof(int));
// use scratch space...
reset_temporary_allocator(); // ready for next use
}A stream I/O library with custom format specifiers (using {} syntax similar to Rust/Python).
Define MY_STREAM_IMPL in exactly one TU.
typedef struct stream stream_t;
struct stream_interface {
int (*close)(void *data);
int (*read)(void *data, unsigned char *out_buf, size_t amount);
int (*write)(void *data, const unsigned char *in, size_t count);
int (*seek)(void *data, long int offset, int whence);
int (*flush)(void *data);
};
typedef struct mem_stream {
char *buffer;
size_t len, pos;
} mem_stream_t;
// Format modifier parser state
typedef struct modifier_stream {
const char *current;
size_t len;
} modifier_stream_t;
// Parsed format modifier info
typedef struct standard_format_info {
int has_len, has_base, has_preci, has_width;
int len, base, preci, width;
int alternate_form, zero_padded;
} standard_format_info_t;
// Custom format specifier callback
typedef int (*format_fn_t)(stream_t stream, modifier_stream_t mod, va_list args);
// A single named format specifier
typedef struct format_specifier {
const char *specifier;
format_fn_t format;
} format_specifier_t;
// Registry of named format specifiers
typedef struct format_specifiers {
size_t len, cap;
format_specifier_t *items;
} format_specifiers_t;Use {specifier} or {specifier:modifiers}.
Modifier syntax: [flags][len/base][.precision]w[width]
Available specifiers (register with define_format_specifier):
| Specifier | Type |
|---|---|
c | char |
s | string (with escape via #) |
i, d, int | int |
li, ld | long int |
lli, lld | long long int |
u | unsigned int |
lu | unsigned long |
llu | unsigned long long |
i8..=i64=, int8..=int64= | (u)intN_t |
u8..=u64=, unt8..=unt64= | unsigned (u)intN_t |
z, usize, iz, isize | size_t |
f, lf, Lf | float, double, long double |
p, ptr | void* (printed as hex) |
Built-in streams: sout, serr, stin (need setup_io_stream()).
| Function / Macro | Description |
|---|---|
setup_io_stream() | Init sout, serr, stin and register built-in specifiers. |
print(fmt, ...) | Print to stdout. |
println(fmt, ...) | Print to stdout with newline. |
eprint(fmt, ...) | Print to stderr. |
eprintln(fmt, ...) | Print to stderr with newline. |
sprint(stream, fmt, ...) | Print to a stream. |
sprintln(stream, fmt, ...) | Print to a stream with newline. |
snsprint(buf, n, fmt, ...) | Print to a fixed-size buffer. |
sputc(stream, ch) | Write a character. |
sputs(stream, s) | Write a string. |
sopen(path, mode) | Open a file as a stream. |
smemopen(buf, len) | Open a memory buffer as a stream. |
sclose(stream) | Close a stream. |
define_format_specifier(name, fn) | Register a custom format specifier. |
find_format_specifier(name, out) | Look up a format specifier. |
vprint(fmt, args) | Varargs version of print. |
vsprint(stream, fmt, args) | Varargs version of sprint. |
vsnsprint(buf, n, fmt, args) | Varargs version of snsprint. |
sread(stream, out, size, n) | Read from a stream. |
swrite(stream, in, size, n) | Write to a stream. |
sseek(stream, offset, whence) | Seek on a stream. |
sflush(stream) | Flush a stream. |
snputs(stream, len, s) | Write a string of known length. |
parse_format_info(mod, args) | Parse a modifier string into standard_format_info_t. |
has_modifier(mod) | Check if a modifier stream has remaining characters. |
peek_modifier(mod) | Peek at the next modifier character without consuming. |
advance_modifier(mod) | Consume and return the next modifier character. |
check_modifier(mod, ch) | Check if the next modifier char equals ch (no consume). |
match_modifier(mod, ch) | If the next modifier char equals ch, consume it and return 1. |
#define MY_STREAM_IMPL
#include "my_stream.h"
int main(void) {
setup_io_stream();
print("Hello, {s}!\n", "World");
println("Value = {i:#02w16}", 42); // binary, zero-padded width 16
println("Pointer = {p}", &main);
// Custom specifier
define_format_specifier("sq", format_int); // "sq" now works like "int"
// Memory stream
char buf[64];
stream_t mem = smemopen(buf, sizeof(buf));
sprint(mem, "written to memory: {sq}", 123);
sclose(mem);
printf("%s\n", buf); // "written to memory: 123"
}UTF-8 validation, decoding, and iteration.
Define UTF8_IMPLEMNTATION (note the typo is in the original) before include.
| Function / Macro | Description |
|---|---|
utf8_iter_init(iter, str) | Initialize an iterator. Returns false if invalid UTF-8. |
utf8_next(iter) | Advance to next codepoint. Returns byte length consumed, or -1 on error. |
utf8_prev(iter) | Get previous codepoint. |
utf8_peek(iter) | Get current codepoint without advancing. |
decode_utf8(str, out) | Decode one codepoint, return bytes consumed. |
is_valid_utf8_cstr(str) | Check if a C string is valid UTF-8. |
get_utf8_char_length(first_byte) | Return expected byte length of the UTF-8 character. |
#define UTF8_IMPLEMNTATION
#include "utf8.h"
int main(void) {
const char *text = "Hello, 世界! 🌍";
// Validation
if (!is_valid_utf8_cstr((const unsigned char *)text)) {
fprintf(stderr, "Invalid UTF-8\n");
return 1;
}
// Iteration
utf8_iter_t iter;
utf8_iter_init(&iter, text);
while (utf8_peek(iter) != 0) {
uint32_t cp = utf8_peek(iter);
printf("U+%04X ", cp);
utf8_next(&iter);
}
printf("\n");
// Output: U+0048 U+0065 U+006C ... U+4E16 U+754C U+0020 U+1F30D
}