A type-safe dynamic array implementation for C.
Forked from the archived project: https://github.com/rxi/vec
- Merged some downstream contributions
- Cleaned up the docs
- Added tests and polish
- MIT License
- Modular - configure allocation size, functions, and behavior
- Small
- Type-safe
- Efficient
- Functional APIs: map, fold, each
The vec.c and vec.h files can be dropped into an existing C project and compiled along with it. If no overrides are required, vec_config_default.h should also be supplied.
Before using a vector it should first be initialized using the vec_init()
function. To release vector memory ensure that vec_deinit()
is always called.
vec_int_t v;
vec_init(&v);
vec_push(&v, 123);
vec_push(&v, 456);
vec_deinit(&v);
To access the elements of the vector directly the vector's data
field can be
used.
if (vec_length(&v) >= 2)
printf("%d\n", v.data[1]); /* Prints the value at index 1 */
The vector memory can be queried using helper macros.
printf("%zu\n", vec_length(&v)); /* Prints the length of the vector */
printf("%zu\n", vec_capacity(&v)); /* Prints the capacity of the vector */
printf("%zu\n", vec_available(&v)); /* Prints the available elements of the vector */
If the vector contains dynamic memory it's simple to release all contained values.
vec_str_t strs;
vec_init(&strs);
vec_push(&strs, stdrup("Hello"));
vec_push(&strs, stdrup("World"));
vec_each(&strs, free);
vec_deinit(&strs);
Configuration is available through the VEC_CONFIG_H compile option. There are overrides for
- Array allocation strategy:
VEC_INIT_CAPACITY
,VEC_GROW_CAPACITY
- Memory allocation:
VEC_MALLOC
,VEC_FREE
,VEC_REALLOC
- Array sizes types:
vec_size_t
- Structure alignment:
VEC_PRE_ALIGN
,VEC_POST_ALIGN
- API function call semantics:
VEC_API
vec.h provides the following predefined vector types:
Contained Type | Type name |
---|---|
void* |
vec_void_t |
char* |
vec_str_t |
int_t |
vec_int_t |
int32_t |
vec_int32_t |
uint32_t |
vec_uint32_t |
int64_t |
vec_int64_t |
uint64_t |
vec_uint64_t |
char |
vec_char_t |
uint8_t |
vec_uint8_t |
float |
vec_float_t |
double |
vec_double_t |
To define a new vector type the vec_define_fields()
macro should be used:
/* Creates the type uint_vec_t for storing unsigned ints */
typedef struct { vec_define_fields(unsigned int) } uint_vec_t;
/* Add vector functionality to a model chunk type */
typedef struct { float x, y, z } point3f_t;
struct vec_chunk {
struct vec_chunk *next;
chunk_header_t header;
vec_define_fields(point3f_t);
} vec_chunk_t;
To preserve the type expression across calls, vector functions are macros. The parameter
v
in each function must be a pointer to a structure that contains the vector fields.
Most APIs have a pointer version and may also have a reverse iteration option. The
arguments of each API are expanded in a macro, to avoid side effects in your arguments.
Avoid side effects. The following actually increments i
twice.
vec_insert(&v, i++)
Embeds the required vec structure fields for values of type T
.
typedef VEC_PRE_ALIGN struct { vec_define_fields(double) } vec_double_t VEC_POST_ALIGN;
Fields can be embedded in any struct - additional fields can be defined and will not be affected by the vec API. See test_vec_custom.c for an example.
Initialize the fields of a vector, this must be called before the vector can be used. This function
only clears the fields of the vector and does not allocate memory. If the fields of the
vector are contained in another structure simply use memset(ptr, 0, sizeof(mystruct))
or mystruct *p = calloc(1, sizeof(mystruct))
are sufficient.
Initialize the fields of a vector with a pre-existing fixed-type array. This allocation will not be grown by default.
int arr[32];
vec_int_t v;
vec_init_with_fixed(&v, arr, vec_countof(arr));
assert(vec_capacity(&v) == vec_countof(arr))
Initializes the fields of a vector with a pre-existing fixed type array, allowing reallocation. When the length of the array exceeds the provided capacity the data will be reallocated preserving existing items.
When capacity is exceeded the vector will allocate a new larger buffer.
int arr[32];
vec_int_t v;
vec_init_with_realloc(&v, arr, vec_countof(arr));
for (int i = 0; i < 34; ++i) { vec_push(&v, i); }
assert(vec_length(&v) == 33);
De-initializes the vector, freeing the memory of the vector allocated during use.
Pushes a value to the end of the vector. Returns VEC_OK
if the operation was
successful, otherwise VEC_ERR
is returned and the vector remains unchanged.
Removes the last element and returns the length of the vector. Will return 0 on an empty array.
Removes the number of values specified by count
, starting at the index
start
.
vec_splice(&v, 2, 4); /* Removes the values at indices 2, 3, 4, and 5 */
Removes the number of values specified by count
, starting at the index
start
; the removed values are replaced with the last count
values of the
vector. This does not preserve ordering but is O(1).
Inserts the value val
at index idx
shifting the elements after the index
to make room for the new value.
/* Inserts the value 123 at the beginning of the vec */
vec_insert(&v, 0, 123);
Returns 0 if the operation was successful, otherwise -1 is returned and the vector remains unchanged.
Sorts the values of the vector; fn
should be a qsort-compatible compare
function.
Performs a binary search for key
on the vector; the elements must be in sorted
order according to the qsort-compatible function fn
. The value pointed to by
idx
is filled with an index of key
if found, -1 if not found.
/* Searches the vector for the key 4*/
int key = 4;
int idx = -1;
vec_bsearch(&v, &key, &idx, compareIntegers);
/* idx != -1 if key was found... */
Swaps the values at the indices idx1
and idx2
with one another.
Truncates the vector's length to 0
or len
. In the case of vec_truncate
, if
len
is greater than the vector's current length then no change is made.
Returns the first or last value in the vector. This should not be used on an empty vector as it performs a direct index into the data.
Reserves capacity for at least n
elements in the given vector; if n
is
less than the current capacity then vec_reserve()
does nothing. Returns 0 if
the operation was successful, otherwise, -1 is returned and the vector remains
unchanged.
Reduces the vector's capacity to the smallest size required to store its current number of values. Returns 0 if the operation is successful, otherwise -1 is returned and the vector remains unchanged.
Extend v
by multiple source elements.
Pushes the contents of the array arr
or the vector v2
to the end of the vector v
.
These operations will grow the underlying array. If the reallocation fails the array
will be partially filled and vec_oom
will return 1
.
Returns true when the underlying vector has experienced an out-of-memory condition.
Swap the data from src
into dst
. Any data that exists in dst
will be de-initialized and
freed first. After the swap src
will be initialized to an empty state.
Finds the first or last occurrence of the value val
in the vector.
idx
should be an int where the value's index will be written;idx
is set to VEC_NOT_FOUND ifval
cannot be found in the vector.
Removes the first occurrence of the value val
from the vector. If the val
is not contained in the vector then vec_remove()
does nothing.
Reverses the order of the vector's values in place. For example, a vector
containing 4, 5, 6
would contain 6, 5, 4
after reversing.
For-each macro expand to the initial portion of a for loop allowing in-place ordered iteration forwards or reverse with the value and iterator (index) available locally for a loop body.
var
should be a variable of the vector's contained type which stores the value on each iteration.iter
should bevec_size_t
used to store the index during iteration._rev
versions of foreach iterate in reverse fromlength-1
to0
_ptr
versions of foreach take the pointer of the element intovar
/* Iterates and prints the value and index of each value in the float vec */
vec_size_t i; float val;
vec_foreach(&v, val, i) {
printf("%zu : %f\n", i, val);
}
/* Iterates and prints the value and index of each value in the float vector */
vec_size_t i; float *val;
vec_foreach_ptr(&v, val, i) {
printf("%zu : %f\n", i, *val);
}
vec_each
iteration is a simplified functional form of foreach. This form is ideal when you
just need to call a function on each element by value or pointer. Additional parameters
can be passed to the function after the value by including them in the macro call.
/* For vectors of pointers it is common to need to free each element before vec_deinit */
vec_each(&v, free);
/* It's easy to also pass along an argument to a functional iterator */
void my_system(component_t *c, arg_t *a) {}
typedef struct { vec_define_fields(component_t) } vec_component_t;
vec_component_t v;
// ...
arg_t arg = { ... };
vec_each(&v, my_system, &arg);
_ptr
takes the address of the value position as an argument tof
vec_map
allows mapping from one vector to another using a function and optional arguments.
Example:
int convert(int x, int c) { /* transform x */ }
vec_int_t v, v2;
/* initialize and push values into v */
vec_map(&v2, &v, convert, 100); /* converts each element in v -> v2 using the argument 100*/
_ptr
passes the value to the function by pointer_rev
reverses the iteration oversrc
- arguments after
f
are passed along afterv
vec_fold
provides a type-safe combination of the output value ov
over the vector v
_ptr
passes the value to the fold function by pointer- arguments after
f
are passed along afterv
vec_fold_expr
generalizes the fold as an expression over the elements of the vector
anything that is a valid expr over the vector data may be used. The expression will
receive each value and the arguments after expr
.
This library is free software; you can redistribute it and/or modify it under the terms of the MIT license. See LICENSE for details.