 
A high-performance, (optionally atomic) reference-counted dynamic array library for C with support for element retain/release functions. Designed for maximum portability across PC and microcontroller targets, with special support for language interpreters and reference-counted object systems.
Features
High Performance
- Lock-free atomic reference counting
- Configurable growth strategies (fixed increment or doubling)
- Zero-copy data access with direct pointer operations
Memory Safe
- Reference counting prevents memory leaks and use-after-free
- Element retain/release functions for complex object lifecycle management
- Perfect for language interpreters with garbage-collected objects
- Automatic cleanup when last reference is released
- Pointers always NULLed after release for safety
Developer Friendly
- Type-safe macros with
typeof
support
- JavaScript-like array semantics
- Comprehensive assert-based error checking
- Single header-only library
Cross-Platform
- Works on Linux, Windows, macOS
- Microcontroller support: Raspberry Pi Pico, ESP32-C3, ARM Cortex-M
- Both GCC and Clang compatible
- Graceful fallback for compilers without
typeof
Atomic reference counting
- Optional atomic reference counting (C11 required)
- Lock-free performance on multi-core systems
- Safe sharing between threads without mutexes
Functional Programming
- Filter, map, and reduce operations with exact capacity allocation
- Single-pass optimizations for memory efficiency
- Perfect for data processing pipelines
Builder Pattern
- Efficient construction with pre-allocation
- Bulk append operations for performance
- Zero-waste capacity management
What's New
v0.3.0 (Latest) - Element Retain/Release System
BREAKING CHANGES - Major API update for language interpreter support
New Element Memory Management
- Added retain/release function pointers to array structure for complex object lifecycle management
da_create()
now takes retain_fn and release_fn parameters: da_create(element_size, capacity, retain_fn, release_fn)
- New
da_new()
function for simple arrays without retain/release: da_new(element_size)
- All element copying operations (copy, concat, slice, append) now call retain_fn on copied elements
- Perfect for language interpreters with reference-counted objects (like Metal interpreter's cell_t)
Updated API
Comprehensive Testing
- All 129+ tests updated and passing
- Added extensive destructor tests for complex object scenarios
- Fixed double-free issues and memory leak scenarios
- Validated retain/release functionality across all operations
v0.1.1
Type Inference Support (Thanks to @Maqi-x via PR #1)
- Added automatic type inference for C23, C++11, and GCC/Clang
- Macros like
DA_PUSH
now work without explicit type parameter when compiler supports it
- Backward compatible - older compilers still work with explicit types
- Improved developer ergonomics with modern C/C++ standards
API Improvements
- Renamed
DA_LEN
/DA_CAP
to DA_LENGTH
/DA_CAPACITY
for clarity
- Similarly renamed builder macros for consistency
- Enhanced documentation with comprehensive examples
v0.1.0
Functional Programming Complete
- Added
da_reduce()
function with accumulator pattern for array reduction
- Completes the functional programming trinity: filter → map → reduce
- User-controlled memory management with result buffers
Builder Pattern Enhancements
Performance Optimizations
- Optimized
da_filter()
to use builder pattern internally (single-pass filtering)
- Improved memory efficiency across all operations
- Exact capacity allocation throughout the API
Comprehensive Testing
- Expanded test suite to 110+ tests (from 20+)
- Complete coverage of all new functions and edge cases
- Enhanced reliability and stability validation
v0.0.1 (Initial Release)
- Core dynamic array functionality with reference counting
- Atomic ref counting
- Type-safe macros and cross-platform compatibility
- Basic builder pattern implementation
- Initial filter and map operations
Quick Start
#define DA_IMPLEMENTATION
int main() {
int value =
DA_AT(arr, 0,
int);
data[1] = 100;
int sum = 0, total;
da_reduce(doubled, &sum, &total, sum_reducer, NULL);
return 0;
}
reference-counted (optionally atomic) dynamic arrays with ArrayBuffer-style builder
DA_DEF void * da_data(da_array arr)
Gets direct pointer to the underlying data array.
Definition dynamic_array.h:1562
DA_DEF void da_release(da_array *arr)
Releases a reference to an array, potentially freeing it.
Definition dynamic_array.h:1527
DA_DEF da_array da_retain(da_array arr)
Increments reference count for sharing an array.
Definition dynamic_array.h:1550
#define DA_AT(arr, i, T)
Type-safe element access by value.
Definition dynamic_array.h:1269
#define DA_PUSH(arr, val, T)
Type-safe element append (adaptive macro)
Definition dynamic_array.h:1262
DA_DEF void da_reduce(da_array arr, const void *initial, void *result, void(*reducer)(void *accumulator, const void *element, void *context), void *context)
Reduces array to single value using accumulator function.
Definition dynamic_array.h:2262
DA_DEF da_array da_filter(da_array arr, int(*predicate)(const void *element, void *context), void *context)
Creates a new array containing elements that pass a predicate test.
Definition dynamic_array.h:2210
DA_DEF da_array da_map(da_array arr, void(*mapper)(const void *src, void *dst, void *context), void *context)
Creates a new array by transforming each element using a mapper function.
Definition dynamic_array.h:2230
#define DA_BUILDER_TO_ARRAY(builder)
Convert builder to array (shorthand for da_builder_to_array)
Definition dynamic_array.h:1449
#define DA_BUILDER_CREATE(T)
Type-safe builder creation.
Definition dynamic_array.h:1423
DA_DEF void da_builder_append_array(da_builder builder, da_array arr)
Appends all elements from an array to the builder.
Definition dynamic_array.h:1886
DA_DEF void da_builder_reserve(da_builder builder, int new_capacity)
Ensures the builder has at least the specified capacity.
Definition dynamic_array.h:1875
Reference-counted dynamic array structure.
Definition dynamic_array.h:192
ArrayBuffer-style builder for efficient array construction.
Definition dynamic_array.h:208
Advanced: Arrays with Element Retain/Release Functions
For complex objects that need custom memory management (like reference-counted interpreter values):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
int id;
char* name;
} Person;
void person_retain(void* p) {
Person* person = (Person*)p;
if (person->name) {
size_t len = strlen(person->name);
char* new_name = malloc(len + 1);
strcpy(new_name, person->name);
person->name = new_name;
}
}
void person_release(void* p) {
Person* person = (Person*)p;
if (person->name) {
free(person->name);
person->name = NULL;
}
}
int main() {
Person alice = {1, malloc(16)};
strcpy(alice.name, "Alice");
return 0;
}
#define DA_CREATE(T, cap, retain_fn, release_fn)
Type-safe array creation.
Definition dynamic_array.h:1245
DA_DEF da_array da_copy(da_array arr)
Creates a complete copy of an existing array.
Definition dynamic_array.h:2175
Language Interpreter Integration
Perfect for language interpreters with reference-counted objects:
extern void cell_retain(void* cell_ptr);
extern void cell_release(void* cell_ptr);
typedef struct cell cell_t;
cell_t value = new_int32(42);
Configuration
Configure the library before including:
#define DA_MALLOC my_malloc
#define DA_REALLOC my_realloc
#define DA_FREE my_free
#define DA_ASSERT my_assert
#define DA_GROWTH 16
#define DA_ATOMIC_REFCOUNT 1
#define DA_IMPLEMENTATION
API Reference
Creation and Reference Counting
#define DA_NEW(T) da_new(sizeof(T))
void (*retain_fn)(void*), void (*release_fn)(void*));
#define DA_CREATE(T, cap, retain_fn, release_fn) \
da_create(sizeof(T), cap, retain_fn, release_fn)
DA_DEF da_array da_create(int element_size, int initial_capacity, void(*retain_fn)(void *), void(*release_fn)(void *))
Creates a new dynamic array with full configuration.
Definition dynamic_array.h:1503
DA_DEF da_array da_new(int element_size)
Creates a new dynamic array (simple version for general use)
Definition dynamic_array.h:1486
Type-Safe Macros
#define DA_CREATE(T, cap)
#define DA_PUSH(arr, val)
#define DA_PUT(arr, i, val)
#define DA_PUSH(arr, val, T)
#define DA_PUT(arr, i, val, T)
#define DA_AT(arr, i, T)
#define DA_LENGTH(arr)
#define DA_CAPACITY(arr)
#define DA_POP(arr, out_ptr)
#define DA_CLEAR(arr)
#define DA_RESERVE(arr, cap)
#define DA_RESIZE(arr, len)
Raw Functions
DA_DEF void * da_get(da_array arr, int index)
Gets a pointer to an element at the specified index.
Definition dynamic_array.h:1556
DA_DEF void da_set(da_array arr, int index, const void *element)
Sets the value of an element at the specified index.
Definition dynamic_array.h:1567
DA_DEF void da_clear(da_array arr)
Removes all elements from the array.
Definition dynamic_array.h:1686
DA_DEF void da_push(da_array arr, const void *element)
Appends an element to the end of the array.
Definition dynamic_array.h:1587
DA_DEF void da_pop(da_array arr, void *out)
Removes and optionally returns the last element.
Definition dynamic_array.h:1669
DA_DEF void da_reserve(da_array arr, int new_capacity)
Ensures the array has at least the specified capacity.
Definition dynamic_array.h:1710
DA_DEF int da_capacity(da_array arr)
Gets the current allocated capacity of the array.
Definition dynamic_array.h:1705
DA_DEF void da_resize(da_array arr, int new_length)
Changes the array length, growing or shrinking as needed.
Definition dynamic_array.h:1721
DA_DEF int da_length(da_array arr)
Gets the current number of elements in the array.
Definition dynamic_array.h:1700
Lock-free Reference Counting
Enable atomic reference counting:
#define DA_ATOMIC_REFCOUNT 1
#define DA_IMPLEMENTATION
da_retain()
- Atomic reference increment
da_release()
- Atomic reference decrement
- Memory cleanup - Only one thread frees when ref count hits 0
Requires External Synchronization:
- Array modifications (
DA_PUSH
, DA_POP
, da_set
)
- Concurrent access to array contents
Perfect for sharing immutable or read-mostly data between threads!
Growth Strategies
Fixed Growth (recommended for microcontrollers):
Doubling Growth (default, good for desktop):
Error Handling
The library uses assertions for error detection. All errors fail fast:
- Out-of-bounds access → Assert failure
- Memory allocation failure → Assert failure
- Invalid parameters → Assert failure
This is perfect for embedded systems where you want to catch bugs early rather than handle them gracefully.
Platform Support
Tested Platforms:
- Linux (GCC, Clang)
- Windows (MinGW, MSVC)
- Raspberry Pi Pico (arm-none-eabi-gcc)
- Raspberry Pi Zero/4 (GCC)
- ESP32-C3 (Espressif toolchain)
Requirements:
- C11 for atomic reference count operations
<stdlib.h>
, <string.h>
, <assert.h>
<stdatomic.h>
(only if DA_ATOMIC_REFCOUNT=1
)
Building and Testing
The library includes a comprehensive Unity-based test suite:
# Clone with Unity framework
git clone <your-repo>
cd dynamic_array.h
# Add Unity framework to unity/ directory
# Then build and test:
mkdir build && cd build
cmake ..
make
./test_runner
Builder Pattern
#define DA_BUILDER_CREATE(T) da_builder_create(sizeof(T))
#define DA_BUILDER_APPEND(builder, val) da_builder_append(builder, &(val))
void (*retain_fn)(void*), void (*release_fn)(void*));
#define DA_BUILDER_TO_ARRAY(builder) \
da_builder_to_array(builder, NULL, NULL)
#define DA_BUILDER_TO_ARRAY_MANAGED(builder, retain_fn, release_fn) \
da_builder_to_array(builder, retain_fn, release_fn)
DA_DEF da_array da_builder_to_array(da_builder *builder, void(*retain_fn)(void *), void(*release_fn)(void *))
Converts builder to a ref-counted array with exact capacity.
Definition dynamic_array.h:1908
DA_DEF da_builder da_builder_create(int element_size)
Creates a new array builder for efficient construction.
Definition dynamic_array.h:1845
DA_DEF void da_builder_append(da_builder builder, const void *element)
Appends an element to the builder.
Definition dynamic_array.h:1859
All 129+ tests should pass, covering:
- Creation and destruction (both simple and retain/release arrays)
- Reference counting behavior
- Element retain/release function integration
- Double-free prevention and memory leak detection
- Growth strategies and builder patterns
- Type-safe macros
- Functional programming operations
- Edge cases and stress tests
- Atomic reference count validation
Use Cases
Perfect for:
- Interpreters and virtual machines
- Game engines (entity lists, render queues)
- Embedded systems with dynamic data
- Multi-threaded applications sharing read-only data
- Data processing pipelines (ETL operations)
- Scientific computing with functional patterns
- Any C project needing JavaScript-like arrays
Example - Shared Bytecode in Interpreter:
compile_to_bytecode(source, bytecode);
Example - Data Processing Pipeline:
da_array sensor_data = load_sensor_readings();
float stats = {0};
da_reduce(scaled, &stats, &summary, calculate_stats, NULL);
Version History
v0.3.0 (Current) - Element Retain/Release System
BREAKING CHANGES - Major API update for language interpreter support
- API:
da_create()
now takes 4 parameters: da_create(element_size, capacity, retain_fn, release_fn)
- NEW:
da_new()
function for simple arrays without retain/release functions
- NEW:
DA_NEW(T)
macro for convenient simple array creation
- Enhanced: All element copying operations call retain_fn on copied elements
- Enhanced:
da_builder_to_array()
now accepts retain_fn and release_fn parameters
- Fixed: Double-free issues and memory leaks in complex object scenarios
- Testing: All 129+ tests updated and passing with comprehensive retain/release coverage
v0.2.0
- Added:
da_find_index()
- find first element matching predicate function
- Added:
da_contains()
- boolean check for element existence using predicates
- Added:
da_sort()
- sort array with custom comparison function and context
- Improved: All array construction operations use da_builder internally for efficiency
- Documentation: Enhanced Doxygen comments with detailed usage examples for new functions
v0.1.0
- Foundation: Complete single-header dynamic array library
- Features: Reference counting, atomic operations, type-safe macros, builder pattern
- Features: Functional operations (filter, map, reduce)
- Features: Advanced manipulation (slice, concat, reverse, swap)
- Platform Support: C99+, optional C11 atomic operations, microcontroller support
- Documentation: Comprehensive Doxygen documentation with examples
License
This project is dual-licensed under your choice of:
- [MIT License](LICENSE-MIT)
- [The Unlicense](LICENSE-UNLICENSE)
Choose whichever license works best for your project!
Contributing
Contributions welcome! Please ensure:
- All tests pass (
make test
)
- Code follows existing style
- New features include tests
- Maintain compatibility across target platforms
Built for performance, designed for portability, crafted for developers. 🚀