facil.io

#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

c
#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_ATOMIC helpers must be available (included automatically when using fio-stl.h as 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_TYPE shrinks the reference counter to 32 bits (instead of the native word size). Do not combine this with a custom FIO_MEM_FREE that depends on the byte-count argument, because the flex free call reports a size that omits sizeof(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

c
/* 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

c
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

c
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

c
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

c
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

c
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

c
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

c
/* 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

c
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

c
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 caller

The _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_NAME or FIO_ARRAY_NAME inclusions) when used together in one translation unit.
  • Each FIO_REF_NAME definition 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.