The following header shows a way to make a generic dynamic array in C with an array of two pointers:
- the first pointer stores the length of the dynamic array;
- the second pointer points to the data.
So, int *vec[2] = { 0 }; is an empty dynamic array of ints. struct person *people[2] = { 0 }; is an empty dynamic array of people.
(uintptr_t)vec[0] is the length of the array, vec[1] is the array.
The vec_push macro pushes a value at the end of the dynamic array and returns true on success.
This code is C23 with statement expressions (a GNU C feature).
First of all, structs aren't used so you don't have to invent names for them (e.g. there is no IntVec). Since a pointer is used to store the length of the dynamic array (as a uintptr_t), this relies on implementation-defined behavior, that is the uintptr_t length read from a pointer must be the same length that was stored.
Second of all, capacity isn't stored at all. Instead, it's computed on demand when the length of the vec is either zero or a power of two. In this case realloc is called with capacity equal to the next power of two greater than the length. The drawback is that it's more difficult to "reserve" elements: during pushing, when the length reaches a power of two, realloc is called for the next power of two no matter what, so a larger manual reservation is effectively discarded.