Dynamic vector of void* in C
Overall, fairly good.


  Am I doing anything dangerous in this?


Lack of comments

I'd expect at least some commentary about the roll of the functions and how to use them near the function declaration section.

Assume a user will read the header and not delve into implementation.

Unexpected type change

The stored ellement is a void *, changing the return type to const is not good.

// const void* vv_read_index(void_vector *vv, size_t index)
void* vv_read_index(void_vector *vv, size_t index)


Overuse of NULL

vv_pop(), vv_read_index() return NULL as an error indicator and can return NULL as a valid return.  If NULL is not to be allowed (which I think is a bad idea) as a storable void *, vv_push(&amp;vv, NULL) should error.

I'd re-work calls to return an error code on vv_pop(), like vv_err vv_pop(void_vector **vv, void **el).

It seems odd to have 2 forms of error signaling: error codes and NULL.

"default size"

I see no real value in a default size of 16.  Suggest setting aside size == 0 as something special and allow an initial vector size of 0.  Add code to handle doubling array size when size is 0.  

Create a VV_DEFAULT_SIZE 16u if desired for the user to call with new_void_vector(VV_DEFAULT_SIZE).

Overflow

I'd detect overflow in size computations.

Tolerate freeing NULL

free(NULL) is well defined.  I'd expect the same with delete_void_vector(NULL, foo).  Currently code dies on this.


  Is the code clear, what can I do to make it easier to follow?


Naming convention

Recommend to use the same one prefix with all external types, names and functions.  

// void_vector.h
vv.h

// VOID_VECTOR_H
VV_H

// void_vector* new_void_vector(size_t size);
void_vector* vv_new(size_t size);


Or use void_vector instead of vv throughout, not both.

Allocate to the size of the object, not type

Allocating to the size of the referenced object is 1) less error prone, 2) easier to review 3) easier to maintain than allocating to the type.

// void_vector* vv = malloc(sizeof(void_vector) + sizeof(void*) * size);
void_vector* vv = malloc(sizeof *vv + sizeof *vv-&gt;data * size);


Avoid unnecessary suffixes

An L, l, LL, ll is needed a lot less than one thinks.  In the following case, L certainty not needed.  The u is useful though.  I am curios as to why OP thought is was needed - what issue was of concern?

/// #define defualt_size 16ul
#define defualt_size 16u


Use const

const would help convey that the function is not altering *vv

// vv_read_index(void_vector *vv ...
vv_read_index(const void_vector *vv ...



  Is there an obvious way to greatly improve the efficiency?


Allocations only increase as vv_pop() does not reduce things. This can make for an inefficiency of memory usage.

IMO, a more advanced  scheme could decrease by 1/2 when size needed falls below 25% or 33%.


  General style comments


Spell check

// #define defualt_size 16ul
#define default_size 16ul


! vs. &gt;

Minor: With arithmetic concerns, &gt; is more readily understood that ! negation.

// if (!size)
if (size &gt; 0)



  I'm not overly concerned about the contents of void_vector_tb.c.


Avoid naked magic numbers

Replace 10 with #define VV_STR_N 10,  32 with #define VV_STR_SZ 32,

Cast not needed

// while(s = (char*) vv_pop(vv)) {
while(s = vv_pop(vv)) {