Welcome to LibFP’s Documentorial!#

This documentation page is designed less like a direct reference and more like a tutorial. If you would instead prefer a reference… full generated documentation can be found here.

This is the C version of this document the C++ version can be found here.

This library provides “fat pointers” - pointers with embedded size information that enable runtime bounds checking and memory safety. Fat pointers store metadata (size, magic number) in a header just before the actual data, allowing automatic length tracking and validation. All while being fully compliant C pointers.

Features#

  • Automatic bounds tracking for allocated memory

  • Stack and heap allocation support

  • Views (non-owning references to memory regions)

  • Iterator support for range-based operations

  • C and C++ compatibility

Single Header Library#

This library can be used standalone from the rest of the library… however certain functions need FP_IMPLEMENTATION defined before the library is included to get definitions. Be sure to #define FP_IMPLEMENTATION in only one source file!

Basic Usage Example#

// Allocate a fat pointer array, notice that the result is a standard pointer and completely
//	compatible with all interfaces expecting a pointer.
int* arr = fp_malloc(int, 20);
// Access with automatic bounds information
for(size_t i = 0; i < fp_length(arr); i++)
    arr[i] = i * 2;

// Reallocate, the one place where C standard library functions won't work is when
//	reallocating or freeing fp memory... use the fp_ versions instead!
arr = fp_realloc(int, arr, 30);
// Clean up... see the note on fp_realloc
fp_free_and_null(arr);
fp_malloc(type, _size)

Allocate a typed fat pointer on the heap.

int* data = fp_malloc(int, 100);
for(size_t i = 0; i < fp_length(data); i++) {
    data[i] = rand();
}
fp_free_and_null(data);

Parameters:
  • type – Element type

  • _size – Number of elements to allocate

Returns:

Typed pointer to allocated heap memory

Warning

doxygenfunction: Cannot find function “fp_length” in doxygen xml output for project “fp_doxygen” from directory: /home/runner/work/libfp/libfp/build/xml

fp_realloc(type, p, _size)

Reallocate a fat pointer with a new size.

Existing data is preserved up to the minimum of old and new sizes.

float* values = fp_malloc(float, 50);
// ... use values ...
values = fp_realloc(float, values, 100); // Expand to 100 elements
Parameters:
  • type – Element type

  • p – Existing fat pointer

  • _size – New number of elements

Returns:

Reallocated typed pointer (may be different address)

fp_free_and_null(p)

Free a fat pointer and set it to NULL.

In general this macro is safer than using fp_free directly

int* data = fp_malloc(int, 50);
fp_free_and_null(data);
assert(data == NULL);
Parameters:
  • p – Fat pointer to free

Stack Allocation Example#

void process_data() {
    // Stack-allocated fat pointer (automatically freed)
    float* temp = fp_alloca(float, 100);
    // Use it like a normal array
    for(size_t i = 0; i < fp_length(temp); i++)
        temp[i] = i * 1.5f;
    // Automatically freed when function returns
}
fp_alloca(type, _size)

Allocate a typed fat pointer on the stack.

Best practices:

  • Use fp_alloca() for temporary buffers < 10KB

  • Use fp_malloc() for anything larger or with dynamic lifetime

  • Never return stack-allocated pointers

  • Be careful with recursion and stack allocations

void compute() {
    int* numbers = fp_alloca(int, 50);
    for(int i = 0; i < 50; i++) {
        numbers[i] = i * i;
    }
    printf("Length: %zu\n", fp_length(numbers));
    // Automatically freed
}

See also

fp_alloca() for type-safe version

See also

fp_malloc() for heap allocation

See also

fp_is_stack_allocated() to check allocation type

Warning

Stack allocations have limited size. Use fp_malloc() for large allocations.

Warning

Stack memory is only valid within the current scope. Do not return stack-allocated pointers from functions.

Parameters:
  • type – Element type

  • _size – Number of elements to allocate

Returns:

Typed pointer to allocated stack memory

Views Example#

int* data = fp_malloc(int, 100);
// Create a view of a portion of the data
fp_view(int) subrange = fp_view_make(int, data, 10, 20);
// Iterate over the view
fp_view_iterate_named(int, subrange, item)
    printf("%d\n", *item);

fp_free_and_null(data);
fp_view(type)

Define a view type for a specific element type (C version)

fp_view(int) my_view;
fp_view(char) str_view;

Parameters:
  • type – Element type

fp_view_make(type, p, start, length)

Create a view from a range within a fat pointer.

int* arr = fp_malloc(int, 100);

// Create view of elements 10-29 (20 elements)
fp_view(int) middle = fp_view_make(int, arr, 10, 20);

fp_view_iterate(int, middle) {
    *i = 999;
}

Parameters:
  • type – Element type

  • p – Fat pointer

  • start – Starting index (inclusive)

  • length – Number of elements

Returns:

Typed view of the specified range

fp_view_iterate_named(type, view, iter)

Iterate over view with named iterator.

fp_view(float) v = ...;
fp_view_iterate_named(float, v, ptr) {
    *ptr *= 2.0f;
}

Parameters:
  • type – Element type

  • view – View structure

  • iter – Iterator variable name

Basic Dynamic Array Example#

// Create empty dynamic array
fp_dynarray(int) arr = NULL;

// Push elements (automatically grows)
fpda_push_back(arr, 10);
fpda_push_back(arr, 20);
fpda_push_back(arr, 30);

printf("Size: %zu, Capacity: %zu\n", fpda_size(arr), fpda_capacity(arr));

// Access elements
for(size_t i = 0; i < fpda_size(arr); i++)
    printf("%d ", arr[i]);

fpda_free_and_null(arr);
fp_dynarray(type)

Define a dynamic array type.

Creates a typed dynamic array pointer. The array starts as NULL and grows automatically as elements are added.

fp_dynarray(int) integers = NULL;
fp_dynarray(float) floats = NULL;
fp_dynarray(MyStruct) structs = NULL;
Parameters:
  • type – Element type

fpda_push_back(a, _value)

Add element to end of array.

Automatically grows array if needed. Amortized O(1) operation.

fp_dynarray(int) numbers = NULL;

for(int i = 0; i < 100; i++)
    fpda_push_back(numbers, i * i);

assert(fpda_size(numbers) == 100);
assert(numbers[50] == 2500);

fpda_free(numbers);
Parameters:
  • a – Dynamic array

  • _value – Value to add

fpda_size

Alias for fpda_length.

Warning

doxygendefine: Cannot find define “fpda_capacity” in doxygen xml output for project “fp_doxygen” from directory: /home/runner/work/libfp/libfp/build/xml

fpda_free_and_null(da)

Free a dynamic array and set pointer to NULL.

Safer than fpda_free() as it prevents use-after-free.

fp_dynarray(int) arr = NULL;
fpda_push_back(arr, 42);
fpda_free_and_null(arr);
assert(arr == NULL);
Parameters:
  • da – Dynamic array to free

Common Dynamic Array Operations#

fp_dynarray(int) numbers = NULL;

// Reserve capacity
fpda_reserve(numbers, 100);

// Add elements
for(int i = 0; i < 50; i++)
    fpda_push_back(numbers, i);

// Insert in middle
fpda_insert(numbers, 25, 999);

// Delete element
fpda_delete(numbers, 10);

// Pop from back
fpda_pop_back(numbers);

// Clear all elements (keeps capacity)
fpda_clear(numbers);

fpda_free_and_null(numbers);
fpda_reserve(a, _size)

Reserve capacity for at least _size elements.

Ensures the array can hold at least _size elements without reallocation. Does not change the size, only the capacity.

fp_dynarray(int) arr = NULL;

// Reserve space for 1000 elements
fpda_reserve(arr, 1000);

// Add elements without reallocation
for(int i = 0; i < 1000; i++)
    fpda_push_back(arr, i);  // No reallocations

fpda_free_and_null(arr);
Parameters:
  • a – Dynamic array

  • _size – Minimum capacity to reserve

fpda_insert(a, _pos, _val)

Insert element at position.

Elements after _pos are shifted right. O(n) operation.

fp_dynarray(int) arr = NULL;
fpda_push_back(arr, 1);
fpda_push_back(arr, 2);
fpda_push_back(arr, 4);
fpda_push_back(arr, 5);

// Insert 3 at index 2
fpda_insert(arr, 2, 3);

// arr is now: [1, 2, 3, 4, 5]
assert(fpda_size(arr) == 5);
assert(arr[2] == 3);

fpda_free_and_null(arr);
Parameters:
  • a – Dynamic array

  • _pos – Position to insert at

  • _val – Value to insert

fpda_delete(a, _pos)

Delete single element.

fp_dynarray(int) arr = NULL;
for(int i = 0; i < 5; i++)
    fpda_push_back(arr, i * 10);
// arr: [0, 10, 20, 30, 40]

fpda_delete(arr, 2);
// arr: [0, 10, 30, 40]

assert(fpda_size(arr) == 4);

fpda_free_and_null(arr);

Parameters:
  • a – Dynamic array

  • _pos – Position of element to delete

Returns:

Modified a.

fpda_pop_back(a)

Remove last element.

fp_dynarray(int) stack = NULL;
fpda_push_back(stack, 10);
fpda_push_back(stack, 20);
fpda_push_back(stack, 30);

fpda_pop_back(stack);
assert(fpda_size(stack) == 2);
assert(stack[fpda_size(stack) - 1] == 20);

fpda_free_and_null(stack);

Parameters:
  • a – Dynamic array

Returns:

Pointer to removed element (now invalid)

fpda_clear(a)

Clear all elements (keeps capacity)

Sets size to 0 but doesn’t free memory.

fp_dynarray(int) arr = NULL;
fpda_reserve(arr, 100);
for(int i = 0; i < 50; i++)
    fpda_push_back(arr, i);

assert(fpda_size(arr) == 50);
assert(fpda_capacity(arr) == 100);

fpda_clear(arr);

assert(fpda_size(arr) == 0);
assert(fpda_capacity(arr) == 100);  // Capacity unchanged

fpda_free_and_null(arr);
Parameters:
  • a – Dynamic array

Advanced Dynamic Array Features#

fp_dynarray(float) data = NULL;

// Grow and initialize
fpda_grow_and_initialize(data, 10, 3.14f);

// Swap elements
fpda_swap(data, 0, 9);

// Clone array
fp_dynarray(float) copy = fpda_clone(data);

// Concatenate arrays
fpda_concatenate_in_place(data, copy);

// Shrink to fit (free excess capacity)
fpda_shrink_to_fit(data);

fpda_free_and_null(data);
fpda_free_and_null(copy);
fpda_grow_and_initialize(a, _to_add, _value)

Grow array and initialize new elements.

fp_dynarray(int) arr = NULL;
fpda_push_back(arr, 1);
fpda_push_back(arr, 2);

// Add 5 more elements, all initialized to 99
fpda_grow_and_initialize(arr, 5, 99);

assert(fpda_size(arr) == 7);
assert(arr[2] == 99);
assert(arr[6] == 99);

fpda_free_and_null(arr);

Parameters:
  • a – Dynamic array

  • _to_add – Number of elements to add

  • _value – Value to initialize new elements with

fpda_swap(a, _pos1, _pos2)

Swap two elements.

fp_dynarray(int) arr = NULL;
fpda_push_back(arr, 10);
fpda_push_back(arr, 20);
fpda_push_back(arr, 30);

fpda_swap(arr, 0, 2);
// arr: [30, 20, 10]

assert(arr[0] == 30);
assert(arr[2] == 10);

fpda_free_and_null(arr);

Parameters:
  • a – Dynamic array

  • _pos1 – First position

  • _pos2 – Second position

fpda_clone(src)

Create a clone of dynamic array.

fp_dynarray(int) original = NULL;
for(int i = 0; i < 5; i++)
    fpda_push_back(original, i * i);

fp_dynarray(int) clone = fpda_clone(original);

clone[0] = 999;  // Doesn't affect original
assert(original[0] == 0);
assert(clone[0] == 999);

fpda_free_and_null(original);
fpda_free_and_null(clone);

Parameters:
  • src – Source array

Returns:

New array with copied data

fpda_concatenate_in_place(dest, src)

Concatenate two arrays in place.

fp_dynarray(int) arr1 = NULL;
fp_dynarray(int) arr2 = NULL;

fpda_push_back(arr1, 1);
fpda_push_back(arr1, 2);
fpda_push_back(arr2, 3);
fpda_push_back(arr2, 4);

fpda_concatenate_in_place(arr1, arr2);
// arr1: [1, 2, 3, 4]
// arr2: [3, 4] (unchanged)

fpda_free_and_null(arr1);
fpda_free_and_null(arr2);

Parameters:
  • dest – Destination array (modified)

  • src – Source array

fpda_shrink_to_fit(a)

Shrink capacity to match current size.

Frees unused capacity. Useful after removing many elements.

fp_dynarray(int) arr = NULL;
fpda_reserve(arr, 1000);
for(int i = 0; i < 100; i++)
    fpda_push_back(arr, i);

assert(fpda_capacity(arr) == 1000);
assert(fpda_size(arr) == 100);

fpda_shrink_to_fit(arr);
assert(fpda_capacity(arr) == 100);  // Freed 900 elements worth of memory

fpda_free_and_null(arr);
Parameters:
  • a – Dynamic array

Indices and Tables#