Dynamic vector of void* in C I have implemented a simple dynamic vector library in C. It is a header-only library.

Full library listing -
void_vector.h:

#ifndef VOID_VECTOR_H
#define VOID_VECTOR_H

typedef enum {
    VV_SUCCESS,
    VV_MALLOC_FAILURE
} vv_err;

typedef struct vv {
    size_t length;
    size_t size;
    void *data[];
} void_vector;

void_vector* new_void_vector(size_t size);
vv_err vv_push(void_vector **vv, void *el);
void *vv_pop(void_vector *vv);
const void* vv_read_index(void_vector *vv, size_t index);
void delete_void_vector(void_vector *vv, void (del_el)(void*));

#ifdef VOID_VECTOR_IMPL
#undef VOID_VECTOR_IMPL

#include &lt;stdlib.h&gt;

#define defualt_size 16ul

void_vector*
new_void_vector(size_t size)
{
    if (!size) size = defualt_size;
    void_vector* vv = malloc(sizeof(void_vector) + sizeof(void*) * size);
    if (vv) {
        vv-&gt;size = size;
        vv-&gt;length = 0;
    }
    return vv;
}

vv_err
vv_push(void_vector **vv, void *el)
{
    if ((*vv)-&gt;length &gt;= (*vv)-&gt;size) {
        void_vector *new_vv = realloc((*vv), sizeof(void_vector)
                                            + sizeof(void*) * (*vv)-&gt;size * 2);
        if (!new_vv) return VV_MALLOC_FAILURE;
        (*vv) = new_vv;
        (*vv)-&gt;size *= 2;
    }
    (*vv)-&gt;data[(*vv)-&gt;length] = el;
    (*vv)-&gt;length++;
    return VV_SUCCESS;
}

void*
vv_pop(void_vector *vv)
{
    if(vv-&gt;length == 0) return NULL;
    vv-&gt;length--;
    return vv-&gt;data[vv-&gt;length];
}

const void*
vv_read_index(void_vector *vv, size_t index)
{
    if (index &gt; vv-&gt;length) return NULL;
    return (vv-&gt;data[index]);
}

void
delete_void_vector(void_vector *vv, void (del_el)(void*))
{
    if (!del_el) del_el = &amp;free;
    for (int i = vv-&gt;length; i; i--) {
        del_el(vv-&gt;data[i-1]);
    }
    free(vv);
}

#endif /* VOID_VECTOR_IMPL */
#endif /* VOID_VECTOR_H */


I am using this as test bench - void_vector_tb.c

#include &lt;stdio.h&gt;

#define VOID_VECTOR_IMPL
#include "void_vector.h"

int
main (int argc, char **argv)
{
    void_vector* vv = new_void_vector(4);

    char *strings[10];

    for (int i = 0; i &lt; 10; i++) {
        strings[i] = malloc(32);
        snprintf(strings[i], 32, "This is String: %d", i); 
    }

    for (int i = 0; i &lt; 10; i++) {
        vv_push(&amp;vv, strings[i]);
    }

    for (int i = 0; i &lt; vv-&gt;length; i++) {
        printf("%d:\t%s\n", i+1, vv_read_index(vv, i));
    }

    char *s;
    while(s = (char*) vv_pop(vv)) {
        printf("%s\n", s);
    }

    for (int i = 0; i &lt; 10; i++) {
        vv_push(&amp;vv, strings[i]);
    }

    delete_void_vector(vv, NULL);
}


I am not using a make file. Compilation is performed by gcc -ggdb void_vector_tb.c -o void_vector_tb 

I would greatly appreciate your time and any comments but I'm particularly interested in the following points:


Am I doing anything dangerous in this?
Is the code clear, what can I do to make it easier to follow?
Is there an obvious way to greatly improve the efficiency?
General style comments
I'm not overly concerned about the contents of void_vector_tb.c.


I ran this through valgrind and got the following output:

==7009== HEAP SUMMARY:
==7009==     in use at exit: 0 bytes in 0 blocks
==7009==   total heap usage: 14 allocs, 14 frees, 1,616 bytes allocated
==7009== 
==7009== All heap blocks were freed -- no leaks are possible
==7009== 
==7009== For counts of detected and suppressed errors, rerun with: -v
==7009== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)


I am trying to minimize use of external libraries (although I don't think it would be possible to avoid stdlib.h).
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)) {