A header-behind-pointer dynamic array implementation
c dynamic array
C 77%
Just 15%
Nix 5%
Shell 3%

README.md

dynarr #

A type-safe dynamic array library for C11, using a header-behind-pointer layout so the array pointer can be indexed and passed to functions just like a plain C array.

Setup #

The project uses Nix flakes + direnv:

direnv allow .   # once, to activate the shell

This provides gcc, just, bear, cppcheck, clang-format, and clangd (plus valgrind on Linux).

Building #

just build     # static (.a) and dynamic (.so/.dylib) into .build/
just static    # static only
just dynamic   # dynamic only

Pre-built artifacts are in lib/static/ and lib/dynamic/. To link against them from another project:

gcc -Ipath/to/dynarr/include main.c -Lpath/to/dynarr/lib/static -lmylib

Testing #

just test       # all tests, with ASan + UBSan
just valgrind   # all tests under Valgrind (Linux only)

To run a single test binary directly:

.build/tests/test_dynarr

API reference #

All operations are macros. arr must be a typed pointer variable (e.g. int *arr); it may be reassigned by operations that realloc.

Lifecycle #

Macro Description
da_new(type) Allocate an empty array. Assign to a type * variable.
da_free(arr) Free the array.
da_copy(arr) Return a new, independent copy. The copy is tight: capacity == count.

Adding elements #

Macro Description
da_append(arr, elem) Append one element, growing if needed.
da_extend(arr, ptr, n) Append n elements from a plain C array ptr.
da_reserve(arr, n) Ensure capacity for at least n elements. No-op if already sufficient.

Removing elements #

Macro Description
da_pop(arr) Remove and return the last element.
da_swap_remove(arr, i) O(1) removal: overwrites index i with the last element, then decrements count. Does not preserve order. Bounds-checked with assert.
da_clear(arr) Reset count to 0, keeping the allocation.

Accessing elements #

Macro Description
arr[i] Direct unchecked access (standard C indexing).
da_get(arr, i) Bounds-checked access via assert.
da_last(arr) The last element (no bounds check).

Inspecting state #

Macro Description
da_length(arr) Number of elements (size_t).
da_capacity(arr) Current allocated capacity (size_t).
da_empty(arr) 1 if count == 0, else 0.

Usage example #

#include "dynarr.h"

typedef struct { float x, y; } Vec2;

int main(void) {
    // Basic append and iterate
    int *nums = da_new(int);
    for (int i = 0; i < 10; i++)
        da_append(nums, i);

    for (size_t i = 0; i < da_length(nums); i++)
        printf("%d\n", nums[i]);

    // Bulk load from a plain C array
    int extra[] = {10, 11, 12};
    da_extend(nums, extra, 3);

    // O(1) removal (order not preserved)
    da_swap_remove(nums, 0);

    // Copy, then free both independently
    int *copy = da_copy(nums);
    da_free(nums);
    da_free(copy);

    // Works with any type
    Vec2 *pts = da_new(Vec2);
    da_reserve(pts, 64);
    Vec2 p = {1.0f, 2.0f};
    da_append(pts, p);
    da_free(pts);

    return 0;
}

Other commands #

just fmt            # auto-format all .c/.h files
just fmt-check      # verify formatting without modifying
just cppcheck       # static analysis
just compile-commands  # generate compile_commands.json for clangd
just clean          # remove .build/