#Reference Counting Wrapper — Module 249
Heap-allocate any type with an atomic reference count attached, generated from a name macro.
See also: [← 200 types-overview.md](./200 types-overview.md)
#Setup
#define FIO_REF_NAME my_obj /* required */
#define FIO_REF_TYPE my_obj_s /* optional — defaults to FIO_REF_NAME_s */
#define FIO_REF_CONSTRUCTOR_ONLY /* optional — see below */
#include "fio-stl.h"This generates a thin wrapper struct that sits before the FIO_REF_TYPE object in memory. The caller only ever sees a FIO_REF_TYPE * (or the tagged pointer type if FIO_PTR_TAG_TYPE is set). The reference count, optional metadata, and optional flex-array length are all hidden in the prefix.
Throughout this document REF stands for whatever name you chose (e.g. my_obj).
Requires: the
FIO_ATOMIChelpers must be available (included automatically when usingfio-stl.has a single header).
#Configuration Macros
Set these before #include, after FIO_REF_NAME.
#Wrapped type
| Macro | Default | Effect |
|---|---|---|
FIO_REF_TYPE |
FIO_REF_NAME_s |
The C type to wrap and reference-count. |
#Constructor / destructor naming
| Macro | Effect |
|---|---|
| (not defined) | Generates REF_new2, REF_dup2, REF_free2 — the 2 suffix leaves room for a separately-defined REF_new / REF_free. |
FIO_REF_CONSTRUCTOR_ONLY |
Generates REF_new, REF_dup, REF_free — use this when the ref-counted constructor is the primary constructor. |
#Object lifecycle hooks
| Macro | Default | Effect |
|---|---|---|
FIO_REF_INIT(obj) |
zero-fill if allocator doesn't guarantee it | Called on the newly allocated FIO_REF_TYPE object after allocation. obj is the dereferenced object (not a pointer). |
FIO_REF_DESTROY(obj) |
(nothing) | Called on the FIO_REF_TYPE object just before memory is freed (when the last reference drops). |
#Metadata
| Macro | Default | Effect |
|---|---|---|
FIO_REF_METADATA |
(not defined) | A type to embed as hidden metadata alongside the ref-counted object. Access it via REF_metadata(). |
FIO_REF_METADATA_INIT(meta) |
zero-fill if needed | Called on the metadata field after allocation. |
FIO_REF_METADATA_DESTROY(meta) |
(nothing) | Called on the metadata field just before memory is freed. |
#Flexible array
| Macro | Default | Effect |
|---|---|---|
FIO_REF_FLEX_TYPE |
(not defined) | When defined, the constructor allocates extra memory for a FIO_REF_FLEX_TYPE[] array immediately after the main struct. The constructor accepts a size_t members argument and stores the count internally. The members value is also available inside FIO_REF_INIT. |
Note:
FIO_REF_FLEX_TYPEshrinks the reference counter to 32 bits (instead of the native word size). Do not combine this with a customFIO_MEM_FREEthat depends on the byte-count argument, because the flex free call reports a size that omitssizeof(FIO_REF_TYPE).
#Pointer tagging
If FIO_PTR_TAG_TYPE is defined (globally, before including), all generated functions accept and return FIO_PTR_TAG_TYPE instead of FIO_REF_TYPE *. Tags are applied on allocation (FIO_PTR_TAG) and stripped internally (FIO_PTR_UNTAG).
#Generated API
#Constructor
/* standard */
FIO_REF_TYPE *REF_new(void); /* FIO_REF_CONSTRUCTOR_ONLY */
FIO_REF_TYPE *REF_new2(void); /* default */
/* with FIO_REF_FLEX_TYPE */
FIO_REF_TYPE *REF_new(size_t members);
FIO_REF_TYPE *REF_new2(size_t members);Allocates and zero-initializes the wrapper header, calls FIO_REF_METADATA_INIT (if any), then calls FIO_REF_INIT on the wrapped object. Returns NULL on allocation failure. Initial reference count is 1.
#Increment reference count
FIO_REF_TYPE *REF_dup(const FIO_REF_TYPE *obj); /* FIO_REF_CONSTRUCTOR_ONLY */
FIO_REF_TYPE *REF_dup2(const FIO_REF_TYPE *obj); /* default */Atomically increments the reference count. Returns the same pointer, or NULL if the input is NULL. Thread-safe.
#Decrement reference count / free
void REF_free(FIO_REF_TYPE *obj); /* FIO_REF_CONSTRUCTOR_ONLY */
void REF_free2(FIO_REF_TYPE *obj); /* default */Atomically decrements the reference count. When the count reaches zero, calls FIO_REF_DESTROY(object), then FIO_REF_METADATA_DESTROY(metadata) (if any), then frees the backing memory. Thread-safe. No-op on NULL.
#Metadata access
FIO_REF_METADATA *REF_metadata(FIO_REF_TYPE *obj);Returns a pointer to the hidden metadata field. Only generated when FIO_REF_METADATA is defined.
#Flex array length
uint32_t REF_metadata_flex_len(FIO_REF_TYPE *obj);Returns the number of FIO_REF_FLEX_TYPE members allocated alongside the object. Only generated when FIO_REF_FLEX_TYPE is defined. Returns 0 for NULL.
#Debugging helper
size_t REF_references(FIO_REF_TYPE *obj);Returns the current reference count. Do not use for program logic — the value is inherently unstable in a concurrent context. Useful for assertions and leak-hunting.
#Examples
#Basic reference-counted object
typedef struct { int x; int y; } point_s;
#define FIO_REF_NAME point
#define FIO_REF_TYPE point_s
#define FIO_REF_CONSTRUCTOR_ONLY /* point_new / point_dup / point_free */
#define FIO_REF_INIT(obj) (obj).x = 0; (obj).y = 0
#include "fio-stl.h"
void example(void) {
point_s *p = point_new(); /* ref count = 1 */
p->x = 10; p->y = 20;
point_s *alias = point_dup(p); /* ref count = 2 */
point_free(alias); /* ref count = 1 — not freed yet */
point_free(p); /* ref count = 0 — freed */
}#Using the 2-suffix when a primary constructor already exists
/* Suppose FIO_STR_NAME already generated str_new / str_free. */
#define FIO_REF_NAME str
#define FIO_REF_TYPE str_s
/* No FIO_REF_CONSTRUCTOR_ONLY — generates str_new2 / str_dup2 / str_free2 */
#define FIO_REF_DESTROY(obj) str_destroy(&(obj))
#include "fio-stl.h"
str_s *s = str_new2(); /* ref-counted allocation */
str_write(s, "hello", 5); /* use the string API directly */
str_s *s2 = str_dup2(s);
str_free2(s2);
str_free2(s); /* calls str_destroy, then frees memory */#Hidden metadata
typedef struct { char name[64]; } widget_s;
typedef struct { uint64_t created_at; } widget_meta_s;
#define FIO_REF_NAME widget
#define FIO_REF_TYPE widget_s
#define FIO_REF_CONSTRUCTOR_ONLY
#define FIO_REF_METADATA widget_meta_s
#define FIO_REF_METADATA_INIT(m) (m).created_at = fio_time_real().tv_sec
#include "fio-stl.h"
void example(void) {
widget_s *w = widget_new();
widget_meta_s *m = widget_metadata(w);
printf("created at: %llu\n", (unsigned long long)m->created_at);
widget_free(w);
}#Flexible array suffix
typedef struct { size_t len; double data[]; } vec_s;
#define FIO_REF_NAME vec
#define FIO_REF_TYPE vec_s
#define FIO_REF_CONSTRUCTOR_ONLY
#define FIO_REF_FLEX_TYPE double
#define FIO_REF_INIT(obj) (obj).len = members /* 'members' is in scope */
#include "fio-stl.h"
void example(void) {
vec_s *v = vec_new(8); /* allocates vec_s + 8 doubles */
uint32_t n = vec_metadata_flex_len(v); /* returns 8 */
for (uint32_t i = 0; i < n; i++) v->data[i] = (double)i;
vec_free(v);
}#Memory Layout
[ _wrapper_s header ] [ FIO_REF_TYPE object ] [ FIO_REF_FLEX_TYPE[] ] (optional)
^ ^
hidden prefix pointer returned to callerThe _wrapper_s header holds the reference count (and optional flx_size / metadata fields). Callers never see it — they only receive a pointer to the FIO_REF_TYPE that immediately follows.
The header's declared alignment matches _Alignof(FIO_REF_TYPE), so its size rounds up to a multiple of that alignment and the object that follows it is always correctly aligned. Allocation goes through FIO_MEM_REALLOC_ALIGNED_ with the type's alignment (the allocator treats smaller-than-default requests as the default), so over-aligned types work in every allocator configuration.
#Notes
- All reference-count operations use atomic instructions and are thread-safe.
- The module must be placed after any other type modules it wraps (e.g., after
FIO_STR_NAMEorFIO_ARRAY_NAMEinclusions) when used together in one translation unit. - Each
FIO_REF_NAMEdefinition is independent: you can wrap multiple types in the same file. REF_references()is a debugging aid; its return value is transient and must not drive control flow.