Search code examples
carraysmacrosundefined-behaviordynamic-arrays

Is it UB to cast a pointer to a void pointer and write to it?


I was working on a way to create dynamic arrays in C, and I came up with this solution as a general structure for how I want my functions/macros to work:

//dynarray.h
#define dynarray(TYPE)\
    struct{\
        TYPE *data;\
        size_t size;\
        size_t capacity;\
    }

int dynarray_init_internal(void **ptr, size_t *size, size_t *cap, size_t type_size, size_t count);

#define dynarray_init(ARR, SIZE) dynarray_init_internal(&ARR->data, &ARR->size, &ARR->capacity, sizeof(*ARR->data), SIZE)

//dynarray.c
int dynarray_init_internal(void **ptr, size_t *size, size_t *cap, size_t type_size, size_t count){
    *ptr = malloc(type_size*count);
    if(*ptr == NULL){
        return 1;
    }

    *size = 0;
    *cap = count;
    return 1;
}

Is this an acceptable approach to have a generic function/macro combo that deals with dynamically allocating memory in a type agnostic way?

The only doubts I have about this is that I'm not sure if this is undefined behavior or not. I imagine this could be easily expanded for other functions that are typically expected for a dynamic array structure. The only issue I can see with it is that since it's an anonymous struct you can't pass it as an argument anywhere (easily at least), but that can be easily fixed by creating a dynarray_def(TYPE, NAME) macro which would define a dynamic array struct with NAME and have it hold data of TYPE while still having it work with all the other function/macro style listed above.


Solution

  • This is undefined behavior because you're converting (for example) an int ** to a void ** and dereferencing it to yield a void *. The automatic conversion to/from a void * does not extend to void **. Reading/writing one type as another (in this case, writing a int * as a void *) is in violation.

    The best way to handle this is to make the entire init routine a macro:

    #define dynarray_init(ARR, SIZE) \
    do {\
        (ARR)->data = malloc(sizeof(*(ARR)->data*(SIZE));\
        if ((ARR)->data == NULL){\
            _exit(1);\
        }\
        (ARR)->size = 0;\
        (ARR)->capacity = (SIZE);\
    } while (0)
    

    EDIT:

    If you're looking to shy away from function-like macros, you can instead use a macro to create a function and the struct type it works with:

    #include <stdio.h>
    #include <stdlib.h>
    
    #define dynarray(TYPE)\
    struct dynarray_##TYPE {\
        TYPE *data;\
        size_t size;\
        size_t capacity;\
    };\
    \
    int dynarray_##TYPE##_init(struct dynarray_##TYPE **ptr, size_t count){\
        *ptr = malloc(sizeof(*ptr)*count);\
        if(*ptr == NULL){\
            return 1;\
        }\
        \
        (*ptr)->size = 0;\
        (*ptr)->capacity = count;\
        return 1;\
    }
    
    // generate types and functions    
    dynarray(int)
    dynarray(double)
    
    int main()
    {
        struct dynarray_int *da1;
        dynarray_int_init(&da1, 5);
        // use da1
        struct dynarray_double *da2;
        dynarray_double_init(&da2, 5);
        // use da2
    
        return 0;
    }