Skip to content

A lightweight and easy-to-use vector library for C99, which uses macros to provide a type-safe generic container for storing and manipulating dynamic contiguous blocks of elements.

License

Notifications You must be signed in to change notification settings

GuineaGuy99/c-vec

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 

Repository files navigation

C99 Generic Vec

A lightweight and easy-to-use vector library for C99, which uses macros to provide a type-safe generic container for storing and manipulating dynamic contiguous blocks of elements.

Key features of this library include:

  • 🪶 Lightweight as the codebase is only ~300 significant lines of code.
  • 📄 Header-only so that it's easy to integrate into your existing codebase.
  • 🔒 Type-safe so that you get type-related compiler errors and warnings.
  • 🔢 Behaves like an array allowing for:
    • Indexing with the [] operator.
    • Passing into functions expecting a C array or pointer.

Quick Links

General Usage

You create a vec by declaring a variable of vec_t(T) and then initializing it. From that point on all vector operations are aware of the type and you will get the same type safety for values that C would provide in any other context.

// create a vec of ints
vec_t(int) numbers;
vec_init(&numbers);

// simply push values
vec_push(&numbers, 10);
vec_push(&numbers, 20);
vec_push(&numbers, 30);

// deinitialize to free memory
vec_deinit(&numbers);

To demonstrate the type safety here is what happens when you push some values of various types.

vec_push(&numbers, (my_struct_t){ /* ... */ });
// error: incompatible types when assigning to type ‘int’ from type ‘my_struct_t’

vec_push(&numbers, "Oops");
// warning: assignment to ‘int’ from ‘char *’ makes integer from pointer without a cast [-Wint-conversion]

for reference, this is the same behaviour as an ordinary variable.

int x = (my_struct_t){ /* ... */ };
// error: incompatible types when assigning to type ‘int’ from type ‘my_struct_t’

x = "Oops";
// warning: assignment to ‘int’ from ‘char *’ makes integer from pointer without a cast [-Wint-conversion]

You can iterate over the vec by using vec_len and an ordinary for loop,

for (size_t i = 0; i < vec_len(&numbers); i++)
{
    printf("%d\n", numbers[i]); // note that indexing works
}

Or by using the convenient vec_for_each macro which gives you a pointer to each element.

vec_for_each(&numbers, num)
{
    printf("%d\n", *num);
}

Indexing into the vec works because the variable numbers is actually a pointer to the first element of the vec's buffer, just like a regular C array. This also means you can pass the vector into any function that expects a C array without any casting.

void int_array_sort(int array[], size_t array_len);

// ...

int_array_sort(numbers, vec_len(&numbers));

User Configurable Macros

Some macros can be provided by the user to add custom behaviour to the library. These include:

  • VEC_MALLOC(size) allows the user to provide their own malloc function.
  • VEC_REALLOC(ptr, size) allows the user to provide their own realloc function.
  • VEC_FREE(ptr) allows the user to provide their own free function.
  • VEC_PANIC(reason) allows the user to control the behaviour when an unrecoverable error is encountered. This should always result in program termination and should never be defined to nothing.
  • VEC_ASSERT(condition, reason) allows the user to provide their own assert function, or to define it as nothing for release builds.

Quick Library Overview

// init / free
void vec_init(vec_t(T) *vec);
void vec_deinit(vec_t(T) *vec);
void vec_deinit_with(vec_t(T) *vec, void (*deinit_fn)(T* value));

// getters
size_t vec_len(const vec_t(T) *vec);
size_t vec_cap(const vec_t(T) *vec);

// add / remove operations
void vec_push(vec_t(T) *vec, T value);
void vec_try_push(vec_t(T) *vec, T value);
T *vec_emplace(vec_t(T) *vec);
T *vec_try_emplace(vec_t(T) *vec);
void vec_reserve(vec_t(T) *vec, size_t capacity);
void vec_try_reserve(vec_t(T) *vec, size_t capacity);
void vec_pop(vec_t(T) *vec);
void vec_pop_with(vec_t(T) *vec, void (*deinit_fn)(T* value));
void vec_clear(vec_t(T) *vec);
void vec_clear_with(vec_t(T) *vec, void (*deinit_fn)(T* value));
void vec_remove_swap_at(vec_t(T) *vec, size_t index);
void vec_remove_swap_at_with(vec_t(T) *vec, size_t index, void (*deinit_fn)(T* value));
void vec_remove_shift_at(vec_t(T) *vec, size_t index);
void vec_remove_shift_at_with(vec_t(T) *vec, size_t index, void (*deinit_fn)(T* value));

// iteration macros
vec_for_each(vec_t(T) *vec, value_ptr);
vec_for_each_index(vec_t(T) *vec, value_ptr, index);

// internals
bool vec__has_header(vec_t(T) *vec);
vec__header_t *vec__get_header(vec_t(T) *vec);
void vec__set_header(vec_t(T) *vec, vec__header_t *header);
void vec__grow_or_fail(vec_t(T) *vec, fail_block);
void vec__reserve_or_fail(vec_t(T) *vec, size_t capacity, fail_block);

Documentation

vec_init

Initialize a vec to an empty state. This does not perform any allocations. After initializing a vec it must be deinitialized with vec_deinit or vec_deinit_with.

Parameters

void vec_init(vec_t(T) *vec);
  • vec_t(T) *vec: The uninitialized vec to initialize.

Example Usage

#include "vec.h"

int main(int argc, char **argv)
{
    // declare and initialize vec
    vec_t(int) numbers;
    vec_init(&numbers);
    
    // initialized vec must be deinitialized
    vec_deinit(&numbers);
    
    return 0;
}

Notes

  • Calling anything other than vec_init on a vec before initializing it is undefined behaviour.
  • Calling vec_init on an already initialized vec is undefined behaviour.

vec_deinit

Deinitialize a vec previously initialized by vec_init. This does not deinitialize the elements stored within the vec, to deinitialize both the vec and the elements see vec_deinit_with.

Parameters

void vec_deinit(vec_t(T) *vec);
  • vec_t(T) *vec: The initialized vec to deinitialize.

Example Usage

#include "vec.h"

int main(int argc, char **argv)
{
    // declare and initialize vec
    vec_t(int) numbers;
    vec_init(&numbers);
    
    // initialized vec must be deinitialized
    vec_deinit(&numbers);
    
    return 0;
}

Notes

  • You should call either vec_deinit or vec_deinit_with. Not both.
  • Calling vec_deinit on an already deinitialized vec is undefined behaviour.
  • Calling anything other than vec_init an a vec after deinitializing is undefined behaviour.

vec_deinit_with

Deinitialize a vec and the elements within it by providing a deinitialize function to apply to each element. This is equivalent to manually iterating and deinitializing each element and then calling vec_deinit.

vec_deinit_with(&people, &person_deinit);

// is equivilant to

vec_for_each(&people, person)
{
    person_deinit(person);
}
vec_deinit(&people);

Parameters

void vec_deinit_with(vec_t(T) *vec, void (*deinit_fn)(T* value));
  • vec_t(T) *vec: The initialized vec to deinitialize.
  • void (*deinit_fn)(T* value): The deinitialize function to apply to each element in the vec.

Example Usage

#include <stdint.h>
#include "vec.h"

typedef struct { /* ... */ } person_t;
void person_init(person_t *person, const char *name, uint8_t age);
void person_deinit(person_t *person);

int main(int argc, char **argv)
{
    // declare and initialize vec
    vec_t(person_t) people;
    vec_init(&people);

    // add and initialize a person
    person_t *person = vec_emplace(&people);
    person_init(person, "Alice", 25);
    
    // deinitialize vec and elements
    vec_deinit_with(&people, &person_deinit);

    return 0;
}

Notes

vec_len

Returns the number of elements in the vec.

Parameters

size_t vec_len(const vec_t(T) *vec);
  • const vec_t(T) *vec: The vec to obtain the length from.
  • return: The number of elements in the vec.

Example Usage

#include <stdint.h>
#include <stdio.h>
#include "vec.h"

int main(int argc, char **argv)
{
    // declare and initialize vec
    vec_t(int) numbers;
    vec_init(&numbers);

    // add some numbers
    vec_push(&numbers, 5);
    vec_push(&numbers, 10);
    vec_push(&numbers, 15);

    // print len
    printf("vec_len: %zu\n", vec_len(&numbers));
    // vec_len: 3

    // deinitialize vec
    vec_deinit(&numbers);

    return 0;
}

vec_cap

Returns the number of elements that the vec has capacity to store before it needs to reallocate.

Parameters

size_t vec_cap(const vec_t(T) *vec);
  • const vec_t(T) *vec: The vec to obtain the capacity from.
  • return: The number of elements the vec has capacity for.

Example Usage

#include <stdio.h>
#include "vec.h"

int main(int argc, char **argv)
{
    // declare and initialize vec
    vec_t(int) numbers;
    vec_init(&numbers);

    // add some numbers
    vec_push(&numbers, 5);
    vec_push(&numbers, 10);
    vec_push(&numbers, 15);

    // print capacity
    printf("vec_cap: %zu\n", vec_cap(&numbers));
    // vec_cap: 4

    // deinitialize vec
    vec_deinit(&numbers);

    return 0;
}

vec_push

Copies and appends a new element to the end of the vec.

Pushing is convenient for smaller types such as primitives however for larger types copying into the vec can be slow. Instead consider using vec_emplace to allocate and initialize the element "in place" to avoid copying the value.

If pushing a new element causes vec_len(&vec) > vec_cap(&vec) the vec will reallocate. If reallocating fails a panic will occur, if you want to handle the failure case use vec_try_push.

Parameters

void vec_push(vec_t(T) *vec, T value);
  • vec_t(T) *vec: The vec to push into.
  • T value: The value to append to the end of the vec.

Example Usage

#include <stdio.h>
#include "vec.h"

int main(int argc, char **argv)
{
    // declare and initialize vec
    vec_t(int) numbers;
    vec_init(&numbers);

    // add some numbers
    vec_push(&numbers, 5);
    vec_push(&numbers, 10);
    vec_push(&numbers, 15);

    // print the numbers
    vec_for_each_index(&numbers, num, i)
    {
        printf("numbers[%zu] = %d\n", i, *num);
    }
    // numbers[0] = 5
    // numbers[1] = 10
    // numbers[2] = 15

    // deinitialize vec and elements
    vec_deinit(&numbers);

    return 0;
}

Notes

  • When vec reallocates it may cause the memory location of it's elements to change, this means that any pre-existing pointers to elements will be invalidated and dereferencing those pointers is undefined behaviour.

vec_try_push

Copies and appends a new element to the end of the vec.

Pushing is convenient for smaller types such as primatives however for larger types copying into the vec can be slow. Instead consider using vec_try_emplace to allocate and initialize the element "in place" to avoid copying the value.

If pushing a new element causes vec_len(&vec) > vec_cap(&vec) the vec will reallocate. If reallocating fails the vec will be in the same state it was before pushing and errno will be set to ENOMEM. If you do not care about the failure case use vec_push.

Parameters

void vec_try_push(vec_t(T) *vec, T value);
  • vec_t(T) *vec: The vec to push into.
  • T value: The value to append to the end of the vec.

Example Usage

#include <stdio.h>
#include "vec.h"

int main(int argc, char **argv)
{
    // declare and initialize vec
    vec_t(int) numbers;
    vec_init(&numbers);

    // try add some numbers
    vec_try_push(&numbers, 5);
    vec_try_push(&numbers, 10);
    vec_try_push(&numbers, 15);

    // stop if an error occoured
    if (errno == ENOMEM)
    {
        printf("out of memory\n");
        vec_deinit(&numbers);
        return 1;
    }

    // print the numbers
    vec_for_each_index(&numbers, num, i)
    {
        printf("numbers[%zu] = %d\n", i, *num);
    }
    // numbers[0] = 5
    // numbers[1] = 10
    // numbers[2] = 15

    // deinitialize vec and elements
    vec_deinit(&numbers);

    return 0;
}

Notes

  • When vec reallocates it may cause the memory location of it's elements to change, this means that any pre-existing pointers to elements will be invalidated and dereferencing those pointers is undefined behaviour.

vec_emplace

Allocates a new uninitialized element at the end of the vec and returns a pointer to it for initialization.

This should be preferred over vec_push for larger types where instead of allocating and initializing a value outside the vec and then copying it into the vec, you can instead allocate and initialize the value "in place" avoiding the need to copy.

If emplacing a new element causes vec_len(&vec) > vec_cap(&vec) the vec will reallocate. If reallocating fails a panic will occur, if you want to handle the failure case use vec_try_emplace.

Parameters

T *vec_emplace(vec_t(T) *vec);
  • vec_t(T) *vec: The vec to emplace into.
  • return: A non-null pointer to the uninitialized new element.

Example Usage

#include <stdint.h>
#include <stdio.h>
#include "vec.h"

typedef struct { /* ... */ } person_t;
void person_init(person_t *person, const char *name, uint8_t age);
void person_deinit(person_t *person);

int main(int argc, char **argv)
{
    // declare and initialize vec
    vec_t(person_t) people;
    vec_init(&people);

    // add a person using push
    person_t alice;                   // allocate initial person on stack
    person_init(&alice, "Alice", 25); // initialize person
    vec_push(&people, alice);         // copy person into vec

    // add a person using emplace
    person_t *bob = vec_emplace(&people); // allocate person directly within vec
    person_init(bob, "Bob", 30);          // initialize person

    // deinitialize vec and elements
    vec_deinit_with(&people, &person_deinit);

    return 0;
}

Notes

  • When vec reallocates it may cause the memory location of it's elements to change, this means that any pre-existing pointers to elements will be invalidated and dereferencing those pointers is undefined behaviour.
  • vec_emplace is implemented using the GNU statement expression extension. This is supported by both GCC and Clang but is not supported by MSVC. If you need to support MSVC use vec_push instead.

vec_try_emplace

Allocates a new uninitialized element at the end of the vec and returns a pointer to it for initialization.

This should be preferred over vec_try_push for larger types where instead of allocating and initializing a value outside the vec and then copying it into the vec, you can instead allocate and initialize the value "in place" avoiding the need to copy.

If emplacing a new element causes vec_len(&vec) > vec_cap(&vec) the vec will reallocate. If reallocating fails the vec will be in the same state it was before emplacing, errno will be set to ENOMEM, and NULL will be returned. If you do not care about the failure case use vec_emplace.

Parameters

T *vec_emplace(vec_t(T) *vec);
  • vec_t(T) *vec: The vec to emplace into.
  • return: A pointer to the uninitialized new element or NULL.

Example Usage

#include <stdint.h>
#include <stdio.h>
#include "vec.h"

typedef struct { /* ... */ } person_t;
void person_init(person_t *person, const char *name, uint8_t age);
void person_deinit(person_t *person);

int main(int argc, char **argv)
{
    // declare and initialize vec
    vec_t(person_t) people;
    vec_init(&people);

    // add a person using try emplace
    person_t *alice= vec_try_emplace(&people); // allocate person directly within vec
    if (alice != NULL)
    {
        person_init(alice, "Alice", 25); // initialize person
    }

    // deinitialize vec and elements
    vec_deinit_with(&people, &person_deinit);

    return 0;
}

Notes

  • When vec reallocates it may cause the memory location of it's elements to change, this means that any pre-existing pointers to elements will be invalidated and dereferencing those pointers is undefined behaviour.
  • vec_try_emplace is implemented using the GNU statement expression extension. This is supported by both GCC and Clang but is not supported by MSVC. If you need to support MSVC use vec_push instead.

vec_reserve

Pre-allocate space so that vec has enough capacity to store at least capacity elements.

The vec will reallocate if it does not have enough capacity. If it already has a larger capacity then requested it will do nothing. If reallocating fails a panic will occur, if you want to handle the failure case use vec_try_reserve.

Reserve should be used if you have a rough idea how many elements your vec will store as it can reduce reallocations and improve performance.

Parameters

void vec_reserve(vec_t(T) *vec, size_t capacity);
  • vec_t(T) *vec: The uninitialized vec to initialize.
  • size_t capacity: The number of elements to pre-allocate space for.

Example Usage

#include "vec.h"

int main(int argc, char **argv)
{
    // declare and initialize the vec
    vec_t(int) numbers;
    vec_init(&numbers);

    // pre-allocate space for 100 ints
    vec_reserve(&numbers, 100);

    // initialized vec must be deinitialized
    vec_deinit(&numbers);
    
    return 0;
}

Notes

  • When vec reallocates it may cause the memory location of it's elements to change, this means that any pre-existing pointers to elements will be invalidated and dereferencing those pointers is undefined behaviour.

vec_try_reserve

Pre-allocate space so that vec has enough capacity to store at least capacity elements.

The vec will reallocate if it does not have enough capacity. If it already has a larger capacity then requested it will do nothing. If reallocating fails the vec will be in the same state it was before reserving and errno will be set to ENOMEM. If you do not care about the failure case use vec_reserve.

Reserve should be used if you have a rough idea how many elements your vec will store as it can reduce reallocations and improve performance.

Parameters

void vec_try_reserve(vec_t(T) *vec, size_t capacity);
  • vec_t(T) *vec: The uninitialized vec to initialize.
  • size_t capacity: The number of elements to pre-allocate space for.

Example Usage

#include "vec.h"

int main(int argc, char **argv)
{
    // declare and initialize the vec
    vec_t(int) numbers;
    vec_init(&numbers);

    // pre-allocate space for 100 ints
    vec_try_reserve(&numbers, 100);
    if (errno == ENOMEM)
    {
        // reserving failed
        return 1;
    }

    // initialized vec must be deinitialized
    vec_deinit(&numbers);
    
    return 0;
}

Notes

  • When vec reallocates it may cause the memory location of it's elements to change, this means that any pre-existing pointers to elements will be invalidated and dereferencing those pointers is undefined behaviour.

vec_pop

Removes the last element from the end of the vec.

If the vec is empty an assertion will trigger. If assertions are disabled popping an empty vec is undefined behaviour. You can check if there are elements remaining in the vec with vec_len.

If the element being removed needs to be deinitialized consider using vec_pop_with.

Parameters

void vec_pop(vec_t(T) *vec);
  • vec_t(T) *vec: The non-empty vec.

Example Usage

#include <stdint.h>
#include <stdio.h>
#include "vec.h"

void print_numbers(float *numbers, size_t numbers_len);

int main(int argc, char **argv)
{
    // declare and initialize vec
    vec_t(float) numbers;
    vec_init(&numbers);

    // add numbers
    vec_push(&numbers, 1.0f);
    vec_push(&numbers, 2.0f);
    vec_push(&numbers, 3.0f);

    // print initial numbers
    print_numbers(numbers, vec_len(&numbers));
    // 1.0f, 2.0f, 3.0f

    // pop vec
    vec_pop(&numbers);

    // print numbers
    print_numbers(numbers, vec_len(&numbers));
    // 1.0f, 2.0f

    // deinitialize vec
    vec_deinit(&numbers);

    return 0;
}

vec_pop_with

Deinitializes and removes the last element from the end of the vec.

If the vec is empty the an assertion will trigger. If assertions are disabled popping an empty vec is undefined behaviour. You can check if there are elements remaining in the vec with vec_len.

If the element does not need to be deinitialized use vec_pop

vec_pop_with(&people, &person_deinit);

// is equivilant to

person_deinit(&people[vec_len(&people) - 1]);
vec_pop(&people);

Parameters

void vec_pop_with(vec_t(T) *vec, void (*deinit_fn)(T* value));
  • vec_t(T) *vec: The non-empty vec.
  • void (*deinit_fn)(T* value): The deinitialize function to apply to the element being removed.

Example Usage

#include <stdint.h>
#include <stdio.h>
#include "vec.h"

typedef struct { /* ... */ } person_t;
void person_init(person_t *person, const char *name, uint8_t age);
void person_deinit(person_t *person);
void print_people(const person_t *people, size_t people_len);

int main(int argc, char **argv)
{
    // declare and initialize vec
    vec_t(person_t) people;
    vec_init(&people);
    vec_reserve(&people, 2);

    // add people
    person_t *alice = vec_emplace(&people);
    person_t *bob = vec_emplace(&people);
    person_init(alice, "Alice", 25);
    person_init(bob, "Bob", 30);

    // print initial people
    print_people(people, vec_len(&people));
    // Alice 25, Bob 30

    // pop last element
    vec_pop_with(&people, &person_deinit);

    // print people
    print_people(people, vec_len(&people));
    // Alice 25

    // deinitialize vec and elements
    vec_deinit_with(&people, &person_deinit);

    return 0;
}

vec_clear

Removes all elements from the vec leaving it empty. This does not affect the capacity of the vec.

If you need to deinitialize the elements consider using vec_clear_with.

Parameters

void vec_clear(vec_t(T) *vec);
  • vec_t(T) *vec: The vec to clear.

Example Usage

#include <stdio.h>
#include "vec.h"

int main(int argc, char **argv)
{
    // declare and initialize vec
    vec_t(int) numbers;
    vec_init(&numbers);

    // add some numbers
    vec_push(&numbers, 5);
    vec_push(&numbers, 10);
    vec_push(&numbers, 15);

    // print len
    printf("vec_len: %zu\n", vec_len(&numbers));
    // vec_len: 3

    // clear the vec
    vec_clear(&numbers);

    // print len
    printf("vec_len: %zu\n", vec_len(&numbers));
    // vec_len: 0

    // deinitialize vec
    vec_deinit(&numbers);

    return 0;
}

vec_clear_with

Deinitializes and removes all elements from the vec leaving it empty. This does not affect the capacity of the vec.

If you do not need to deinitialize the elements use vec_clear.

vec_clear_with(&people, &person_deinit);

// is equivilant to

vec_for_each(&people, person)
{
    person_deinit(person);
}
vec_clear(&people);

Parameters

void vec_clear_with(vec_t(T) *vec, void (*deinit_fn)(T* value));
  • vec_t(T) *vec: The vec to clear.
  • void (*deinit_fn)(T* value): The deinitialize function to apply to each element in the vec.

Example Usage

#include <stdint.h>
#include <stdio.h>
#include "vec.h"

typedef struct { /* ... */ } person_t;
void person_init(person_t *person, const char *name, uint8_t age);
void person_deinit(person_t *person);

int main(int argc, char **argv)
{
    // declare and initialize vec
    vec_t(person_t) people;
    vec_init(&people);
    vec_reserve(&people, 2);

    // add people
    person_t *alice = vec_emplace(&people);
    person_t *bob = vec_emplace(&people);
    person_init(alice, "Alice", 25);
    person_init(bob, "Bob", 30);

    // print initial len
    printf("vec_len: %zu\n", vec_len(&people));
    // vec_len: 2

    // clear the vec
    vec_clear_with(&people, &person_deinit);

    // print len
    printf("vec_len: %zu\n", vec_len(&people));
    // vec_len: 0

    // deinitialize vec, note that we don't need to use vec_deinit_with because
    // we know the vec is empty.
    vec_deinit(&people);

    return 0;
}

vec_remove_swap_at

Remove an element at index from the vec. The removed element is replaced by the last element of the vector.

This is an O(1) operation but does not preserve the ordering of elements in the vec. If the order of elements matters use vec_remove_shift_at instead.

If you need to deinitialize the element being removed consider using vec_remove_swap_at_with.

If the provided index is out of bounds an assertion will trigger. If assertions are disabled it is undefined behaviour.

Parameters

void vec_remove_swap_at(vec_t(T) *vec, size_t index);
  • vec_t(T) *vec: The vec to remove the element from.
  • index: The index of the element to remove.

Example Usage

#include <stdint.h>
#include <stdio.h>
#include "vec.h"

void print_numbers(int *numbers, size_t numbers_len);

int main(int argc, char **argv)
{
    // declare and initialize vec
    vec_t(int) numbers;
    vec_init(&numbers);

    // add some numbers
    vec_push(&numbers, 1);
    vec_push(&numbers, 2);
    vec_push(&numbers, 3);
    vec_push(&numbers, 4);
    vec_push(&numbers, 5);

    // print initial numbers
    print_numbers(numbers, vec_len(&numbers));
    // 1, 2, 3, 4, 5

    // remove element at numbers[1] (the number 2)
    vec_remove_swap_at(&numbers, 1);

    // print numbers
    print_numbers(numbers, vec_len(&numbers));
    // 1, 5, 3, 4

    // deinitialize vec
    vec_deinit(&numbers);

    return 0;
}

vec_remove_swap_at_with

Deinitialize and remove an element at index from the vec. The removed element is replaced by the last element of the vector.

This is an O(1) operation but does not preserve the ordering of elements in the vec. If the order of elements matters use vec_remove_shift_at_with instead.

If you do not need to deinitialize the element being removed use vec_remove_swap_at.

If the provided index is out of bounds an assertion will trigger. If assertions are disabled it is undefined behaviour.

vec_remove_swap_at_with(&people, 2, &person_deinit);

// is equivilant to

person_deinit(&people[2]);
vec_remove_swap_at(&people, 2);

Parameters

void vec_remove_swap_at_with(vec_t(T) *vec, size_t index, void (*deinit_fn)(T* value));
  • vec_t(T) *vec: The vec to remove the element from.
  • index: The index of the element to remove.
  • void (*deinit_fn)(T* value): The deinitialize function to apply to the element being removed.

Example Usage

#include <stdint.h>
#include <stdio.h>
#include "vec.h"

typedef struct { /* ... */ } person_t;
void person_init(person_t *person, const char *name, uint8_t age);
void person_deinit(person_t *person);
void print_people(const person_t *people, size_t people_len);

int main(int argc, char **argv)
{
    // declare and initialize vec
    vec_t(person_t) people;
    vec_init(&people);
    vec_reserve(&people, 5);

    // add some people
    person_t *alice = vec_emplace(&people);
    person_t *bob = vec_emplace(&people);
    person_t *charlie = vec_emplace(&people);
    person_t *dave = vec_emplace(&people);
    person_t *eve = vec_emplace(&people);
    person_init(alice, "Alice", 30);
    person_init(bob, "Bob", 31);
    person_init(charlie, "Charlie", 32);
    person_init(dave, "Dave", 33);
    person_init(eve, "Eve", 34);

    // print initial people
    print_people(people, vec_len(&people));
    // Alice 30, Bob 31, Charlie 32, Dave 33, Eve 34

    // remove element at people[1] (Bob)
    vec_remove_swap_at_with(&people, 1, &person_deinit);

    // print people
    print_people(people, vec_len(&people));
    // Alice 30, Eve 34, Charlie 32, Dave 33

    // deinitialize vec and elements
    vec_deinit_with(&people, &person_deinit);

    return 0;
}

vec_remove_shift_at

Remove an element at index from the vec, shifting all elements after the removed element to the left.

This has a worst-case performance of O(n) due to the shifting of elements. If the ordering of elements in the vec is not important use vec_remove_swap_at instead as it is faster.

If you need to deinitialize the element being removed consider using vec_remove_shift_at_with.

If the provided index is out of bounds an assertion will trigger. If assertions are disabled it is undefined behaviour.

Parameters

void vec_remove_shift_at(vec_t(T) *vec, size_t index);
  • vec_t(T) *vec: The vec to remove the element from.
  • index: The index of the element to remove.

Example Usage

#include <stdint.h>
#include <stdio.h>
#include "vec.h"

void print_numbers(int *numbers, size_t numbers_len);

int main(int argc, char **argv)
{
    // declare and initialize vec
    vec_t(int) numbers;
    vec_init(&numbers);

    // add some numbers
    vec_push(&numbers, 1);
    vec_push(&numbers, 2);
    vec_push(&numbers, 3);
    vec_push(&numbers, 4);
    vec_push(&numbers, 5);

    // print initial numbers
    print_numbers(numbers, vec_len(&numbers));
    // 1, 2, 3, 4, 5

    // remove element at numbers[1] (the number 2)
    vec_remove_shift_at(&numbers, 1);

    // print numbers
    print_numbers(numbers, vec_len(&numbers));
    // 1, 3, 4, 5

    // deinitialize vec
    vec_deinit(&numbers);

    return 0;
}

vec_remove_shift_at_with

Deinitialize and remove an element at index from the vec, shifting all elements after the removed element to the left.

This has a worst-case performance of O(n) due to the shifting of elements. If the ordering of elements in the vec is not important use vec_remove_swap_at_with instead as it is faster.

If you do not need to deinitialize the element being removed use vec_remove_shift_at.

If the provided index is out of bounds an assertion will trigger. If assertions are disabled it is undefined behaviour.

vec_remove_shift_at_with(&people, 2, &person_deinit);

// is equivilant to

person_deinit(&people[2]);
vec_remove_shift_at(&people, 2);

Parameters

void vec_remove_shift_at_with(vec_t(T) *vec, size_t index, void (*deinit_fn)(T* value));
  • vec_t(T) *vec: The vec to remove the element from.
  • index: The index of the element to remove.
  • void (*deinit_fn)(T* value): The deinitialize function to apply to the element being removed.

Example Usage

#include <stdint.h>
#include <stdio.h>
#include "vec.h"

typedef struct { /* ... */ } person_t;
void person_init(person_t *person, const char *name, uint8_t age);
void person_deinit(person_t *person);
void print_people(const person_t *people, size_t people_len);

int main(int argc, char **argv)
{
    // declare and initialize vec
    vec_t(person_t) people;
    vec_init(&people);
    vec_reserve(&people, 5);

    // add some people
    person_t *alice = vec_emplace(&people);
    person_t *bob = vec_emplace(&people);
    person_t *charlie = vec_emplace(&people);
    person_t *dave = vec_emplace(&people);
    person_t *eve = vec_emplace(&people);
    person_init(alice, "Alice", 30);
    person_init(bob, "Bob", 31);
    person_init(charlie, "Charlie", 32);
    person_init(dave, "Dave", 33);
    person_init(eve, "Eve", 34);

    // print initial people
    print_people(people, vec_len(&people));
    // Alice 30, Bob 31, Charlie 32, Dave 33, Eve 34

    // remove element at people[1] (Bob)
    vec_remove_shift_at_with(&people, 1, &person_deinit);

    // print people
    print_people(people, vec_len(&people));
    // Alice 30, Charlie 32, Dave 33, Eve 34

    // deinitialize vec and elements
    vec_deinit_with(&people, &person_deinit);

    return 0;
}

vec_for_each

Convenience macro to iterate over the values of a vec. The macro takes two arguments: a pointer to the vec and the name of the value variable to be used for each iteration.

During each iteration, the current value can be accessed using the variable name you specified, which will be a pointer to the current value. For example, in the provided code snippet:

vec_t(float) my_vec;
// ...
vec_for_each(&my_vec, num)
{
    printf("%f\n", *num);
}

The macro is used to loop over the values of my_vec, assigning the address of the current value to the variable num each iteration.

If you need the current index while iterating, use vec_for_each_index instead.

Parameters

vec_for_each(vec_t(T) *vec, value_ptr);
  • vec_t(T) *vec: The vec to iterate.
  • value_ptr: The name of the variable that contains a pointer to the current iterations value.

Example Usage

#include <stdint.h>
#include <stdio.h>
#include "vec.h"

typedef struct { /* ... */ } person_t;
void person_init(person_t *person, const char *name, uint8_t age);
void person_deinit(person_t *person);
void person_print(const person_t *person);

int main(int argc, char **argv)
{
    // declare and initialize vec
    vec_t(person_t) people;
    vec_init(&people);
    vec_reserve(&people, 2);

    // add some people
    person_t *alice = vec_emplace(&people);
    person_t *bob = vec_emplace(&people);
    person_init(alice, "Alice", 25);
    person_init(bob, "Bob", 30);

    // print people using a normal for loop
    for (size_t i=0; i<vec_len(&people); i++)
    {
        person_print(&people[i]);
    }

    // print people using vec_for_each
    vec_for_each(&people, p);
    {
        person_print(p);
    }

    // deinitialize vec and elements
    vec_deinit_with(&people, &person_deinit);

    return 0;
}

Notes

  • vec_for_each is implemented using the GNU typeof extension. This is supported by both GCC and Clang but is not supported by MSVC. If you need to support MSVC use an ordinary for loop instead.

vec_for_each_index

Convenience macro to iterate over the values and indices of a vec. The macro takes three arguments: a pointer to the vec, the name of the value variable to be used for each iteration, and the name if the index variable to be used for each iteration.

During each iteration, the current value and index can be accessed using the variable names you specified, the value variable will be a pointer to the current value, the index will be a size_t. For example, in the provided code snippet:

vec_t(float) my_vec;
// ...
vec_for_each_index(&my_vec, num, i)
{
    printf("my_vec[%zu] = %f\n", i, *num);
}

The macro is used to loop over the values of my_vec, assigning the address of the current value to the variable num and the current index to i each iteration.

If you do not need the current index while iterating, use vec_for_each instead.

Parameters

vec_for_each_index(vec_t(T) *vec, value_ptr, index);
  • vec_t(T) *vec: The vec to iterate.
  • value_ptr: The name of the variable that contains a pointer to the current iterations value.
  • index: The name of the variable that contains the index of current iterations value.

Example Usage

#include <stdint.h>
#include <stdio.h>
#include "vec.h"

typedef struct { /* ... */ } person_t;
void person_init(person_t *person, const char *name, uint8_t age);
void person_deinit(person_t *person);
void person_print(const person_t *person);

int main(int argc, char **argv)
{
    // declare and initialize vec
    vec_t(person_t) people;
    vec_init(&people);
    vec_reserve(&people, 2);

    // add some people
    person_t *alice = vec_emplace(&people);
    person_t *bob = vec_emplace(&people);
    person_init(alice, "Alice", 25);
    person_init(bob, "Bob", 30);

    // print people using a normal for loop
    for (size_t i=0; i<vec_len(&people); i++)
    {
        printf("person #%zu = ", i+1);
        person_print(&people[i]);
    }

    // print people using vec_for_each_index
    vec_for_each_index(&people, p, i);
    {
        printf("person #%zu = ", i+1);
        person_print(p);
    }

    // deinitialize vec and elements
    vec_deinit_with(&people, &person_deinit);

    return 0;
}

Notes

  • vec_for_each_index is implemented using the GNU typeof extension. This is supported by both GCC and Clang but is not supported by MSVC. If you need to support MSVC use an ordinary for loop instead.

vec__has_header

⚠️ This exists for internal use and may be removed without warning. Use at your own risk. ⚠️

Checks if a vec has a header that can be retrieved with vec__get_header.

Parameters

bool vec__has_header(vec_t(T) *vec);
  • vec_t(T) *vec: The vec to check.
  • return: true if vec has a header, false if it does not.

Example Usage

#include <stdio.h>
#include "vec.h"

int main(int argc, char **argv)
{
    // declare and initialize vec
    vec_t(int) numbers;
    vec_init(&numbers);

    // add some numbers
    vec_push(&numbers, 5);
    vec_push(&numbers, 10);
    vec_push(&numbers, 15);

    if (vec__has_header(&numbers))
    {
        vec__header_t *header = vec__get_header(&numbers);

        // print len
        printf("vec_len: %zu\n", header->len);
        // vec_len: 3
    }

    // deinitialize vec
    vec_deinit(&numbers);

    return 0;
}

vec__get_header

⚠️ This exists for internal use and may be removed without warning. Use at your own risk. ⚠️

Gets the header of the vec. Calling vec__get_header(&vec) when !vec__has_header(&vec) is undefined behaviour.

Parameters

vec__header_t *vec__get_header(vec_t(T) *vec);
  • vec_t(T) *vec: The vec to obtain the header of.
  • return: The header of the vec.

Example Usage

#include <stdio.h>
#include "vec.h"

int main(int argc, char **argv)
{
    // declare and initialize vec
    vec_t(int) numbers;
    vec_init(&numbers);

    // add some numbers
    vec_push(&numbers, 5);
    vec_push(&numbers, 10);
    vec_push(&numbers, 15);

    if (vec__has_header(&numbers))
    {
        vec__header_t *header = vec__get_header(&numbers);

        // print len
        printf("vec_len: %zu\n", header->len);
        // vec_len: 3
    }

    // deinitialize vec
    vec_deinit(&numbers);

    return 0;
}

vec__set_header

⚠️ This exists for internal use and may be removed without warning. Use at your own risk. ⚠️

Sets the header of the vec.

Parameters

void vec__set_header(vec_t(T) *vec, vec__header_t *header);
  • vec_t(T) *vec: The vec to set the header of.
  • vec__header_t *header: The header to assign to the vec.

Example Usage

#include <stdio.h>
#include "vec.h"

int main(int argc, char **argv)
{
    // declare and initialize vec
    vec_t(int) numbers;
    vec_init(&numbers);

    // reserve a capacity of 3 elements manually
    vec__header_t *header = VEC_MALLOC(sizeof(vec__header_t) + sizeof(int) * 3);
    if (header != NULL)
    {
        vec__set_header(&numbers, header);
        header->len = 0;
        header->cap = 3;
    }

    // deinitialize vec
    vec_deinit(&numbers);

    return 0;
}

vec__grow_or_fail

⚠️ This exists for internal use and may be removed without warning. Use at your own risk. ⚠️

Allocates an uninitialized new element at the end of the vec. If the new element causes vec_len(&vec) > vec_cap(&vec) the vec will reallocate. If a new element cannot be allocated fail_block is executed.

Parameters

void vec__grow_or_fail(vec_t(T) *vec, fail_block);
  • vec_t(T) *vec: The vec to grow.
  • fail_block: block of code to execute when allocation fails.

Example Usage

#include <stdio.h>
#include "vec.h"

int main(int argc, char **argv)
{
    // declare and initialize vec
    vec_t(int) numbers;
    vec_init(&numbers);

    // grow vec to fit a new element
    vec__grow_or_fail(&numbers, { printf("Oh no!"); return 1; });
    numbers[0] = 10;

    // deinitialize vec
    vec_deinit(&numbers);

    return 0;
}

Notes

  • When vec reallocates it may cause the memory location of it's elements to change, this means that any pre-existing pointers to elements will be invalidated and dereferencing those pointers is undefined behaviour.

vec__reserve_or_fail

⚠️ This exists for internal use and may be removed without warning. Use at your own risk. ⚠️

Pre-allocate space so that vec has enough capacity to store at least capacity elements. The vec will reallocate if it does not have enough capacity. If it already has a larger capacity then requested it will do nothing. If reallocating fails fail_block is executed.

Parameters

void vec__reserve_or_fail(vec_t(T) *vec, size_t capacity, fail_block);
  • vec_t(T) *vec: The vec to grow.
  • size_t capacity: The number of elements to allocate space for.
  • fail_block: block of code to execute when allocation fails.

Example Usage

#include <stdio.h>
#include "vec.h"

int main(int argc, char **argv)
{
    // declare and initialize vec
    vec_t(int) numbers;
    vec_init(&numbers);

    // reserve space for 100 ints.
    vec__reserve_or_fail(&numbers, 100, { printf("Oh no!"); return 1; });

    // deinitialize vec
    vec_deinit(&numbers);

    return 0;
}

Notes

  • When vec reallocates it may cause the memory location of it's elements to change, this means that any pre-existing pointers to elements will be invalidated and dereferencing those pointers is undefined behaviour.

About

A lightweight and easy-to-use vector library for C99, which uses macros to provide a type-safe generic container for storing and manipulating dynamic contiguous blocks of elements.

Topics

Resources

License

Stars

Watchers

Forks

Languages