Search code examples
cpointersgenericsvoid-pointers

Implementation of generic struct with header


I'm a little confused about it. The exercise is very long so I hope I wrote everything that's relevant for my question. I have a given header file (part of it):

typedef void *(*copy_element)(const void *);
typedef void *(*free_element)(void **);

typedef struct group {
    size_t group_size;
    void **data;
    copy_element copy_element_func;
    free_element free_element_func;
} group;

group *group_alloc(copy_element copy_element_func, free_element free_element_func);
void group_free(group **p);
int add(group *group, const void *value);

I need to implement group.c as a generic struct. My question is how can I implement add and alloc functions for **data? With a known type I would use malloc and realloc with the size of the type, but here I'm not sure what to do.

group *group_alloc() {
    group *p = malloc(sizeof(group))
    if(p == NULL) {
        //
    }
    p->group_size = 0;

    void **ptr = malloc(sizeof(void*));
    p->data = ptr;
    return p;
}

In the exercise, Group should contain a dynamic array of values.

Thanks!


Solution

  • In fact, you should not care for the size nor the type of the elements, because the caller shall provide 2 functions that deal with copy and deallocation of those elements, so you can at the group functions level handle them as fully opaque pointers.

    Here is a possible implementation. This code also contains a small demo showing how to handle null terminated strings:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    typedef void *(*copy_element)(const void *);
    typedef void *(*free_element)(void **);
    
    typedef struct group {
        size_t group_size;
        void **data;
        copy_element copy_element_func;
        free_element free_element_func;
    } group;
    
    group *group_alloc(copy_element copy_element_func, free_element free_element_func);
    void group_free(group **p);
    int add(group *group, const void *value);
    
    /***
    Allocate a new group that will use the 2 provided functions.
    The group will be initially empty
    */
    group *group_alloc(copy_element copy_element_func, free_element free_element_func) {
        group * g = malloc(sizeof(*g));
        g->group_size = 0;
        g->data = NULL;
        g->copy_element_func = copy_element_func;
        g->free_element_func = free_element_func;
        return g;
    }
    
    /*********
    * Add a new element to a group.
    * Will use the copy_element_func member to build a copy of the element
    * This implementation returns the number of elements in the group
    */
    int add(group *group, const void *value) {
        size_t sz = group->group_size + 1;  // do not change anything on alloc error
        void **data = realloc(group->data, sz * sizeof(void *));
        if (data == NULL) {  // allocation error
            return 0;
        }
        // use copy_element_func to build a copy of the element
        data[sz - 1] = group->copy_element_func(value);
        group->group_size = sz;
        group->data = data;
        return (int) sz;
    }
    
    /******************
    * Free a group.
    * First free all elements of the group (using the free_element_func member)
    * and then the group itself
    */
    void group_free(group **p) {
        group *g = *p;
        if (g != NULL) {
            for (int i = 0; i < g->group_size; i++) {
                // again use free_element_func that should be able to free an element
                g->free_element_func(g->data + i);
            }
            free(g);
        }
        *p = NULL;
    }
    
    // Example functions for null terminated strings
    void * copy_string(const void *input) {
        return strdup(input);
    }
    
    void * free_string(void **str) {
        free(*str);
        *str = NULL;
        return *str;
    }
    
    // demo code
    int main() {
        group *g = group_alloc(&copy_string, &free_string);
        int i = add(g, "foo");
        printf("%d\n", i);   // should display 1
        i = add(g, "bar");
        printf("%d\n", i);   // should display 2
        for (i = 0; i < g->group_size; i++) {
            printf("%s\n", ((char **)g->data)[i]);  // should display foo then bar
        }
        group_free(&g);
        printf("%p\n", g);   // should display a NULL pointer
        return 0;
    }
    

    Disclaimer: this code blindly assumes the availability of the strdup function, while it is optional and does not test for allocation errors...