Search code examples
arrayscstructcastingvoid-pointers

Pass an element from a structs array and concatenate a string in C


I have a structs array which contains structs that have the following definition:

typedef struct
{
    void *data;
    void (*print)(void *);
    status_ty (*add)(void *, int);
    void (*free_element)(void *);
} element_ty;

Each element can be either an "int" element, a "float" element or a "string" element.

I want to be able to add int values to each element.

For example:

add (3, array, size_of_array);

will loop through the array and the value of 3 will be added to each one of the elements.

Add is always adding an int value, For int and float elements this is normal addition, and for a string it should do concatenation.

For example:

  • if the array contains the following elements: 4.2, 6, "welcome" - then if 3 is added to each element, the result would be: 7.2, 9, "welcome3"

My questions are:

  • How should I pass the element itself to the add function when add gets a void * and I'm passing a pointer type of element_ty (the struct), which will point to this specific element in the array? See my written code below, let me know if there are any mistakes there

This is my pseudo code for the AddToStr function:

  • Receive the int value that should be added
  • Cast the existing string that is located in element -> data to string (?) to be able to concatenate it later? The current data is stored as type of void *.
  • malloc the string to be able to receive the int value?
  • cast it again to void * and store it in element -> data?
  • free() the malloc?

I'm so confused.

This is what I have wrote so far:

    void FreeStrElement(void *(element_ty *element))
{

    free(element -> data);  

}


/*------------------------------------------------------------------*/

    void AddToInt(void *(element_ty *element), int IncrementValue)
{

    int data = (int) element -> data;
    int sum = data + IncrementValue;
    element -> data = (void *) sum; 

}

/*------------------------------------------------------------------*/

void AddToFlt(void *(element_ty *element), int IncrementValue)
{

    float data = (float *) element -> data;
    float sum = data + IncrementValue;
    element -> data = (void *) *(float *)∑
}

/*------------------------------------------------------------------*/

void AddToStr(void *(element_ty *element), int IncrementValue)
{

    ??

}


/*------------------------------------------------------------------*/

void PrintStr(void *num)
{

    printf("String in the element: %s\n", (char **) &num);  

}


/*------------------------------------------------------------------*/

status_ty InitStr(const char *value, element_ty *element)
{
    element -> data = (void *)value;
    element -> print = PrintStr;
    element -> add = AddtoStr;
    element -> free_element = FreeStrElement;
    
}

Solution

  • The functions that are assigned to each element need to take an element* as input, as they need to be able to modify the data member as needed. This is especially important for "string" elements, since you need to be able to reallocate the string memory when adding more characters, which means assigning a new char* pointer to the data.

    You can pass in the element* pointers as void* parameters (which you said is required), but you will have to type-cast the void* back to element* inside the function bodies.

    Try something like this:

    typedef struct
    {
        void *data;
        void (*print)(void *);
        status_ty (*add)(void *, int);
        void (*free_element)(void *);
    } element_ty;
    
    /*------------------------------------------------------------------*/
    
    void FreeStrElement(void *element)
    {
        element_ty *p_element = (element_ty *) element;
        free(p_element->data);
    }
    
    /*------------------------------------------------------------------*/
    
    status_ty AddToInt(void *element, int IncrementValue)
    {
        element_ty *p_element = (element_ty *) element;
        int *p_value = (int*) &(p_element->data);
        *p_value += IncrementValue;
        return ...;
    }
    
    /*------------------------------------------------------------------*/
    
    status_ty AddToFlt(void *element, int IncrementValue)
    {
        element_ty *p_element = (element_ty *) element;
        float *p_value = (float*) &(p_element->data);
        *p_value += IncrementValue;
        return ...;
    }
    
    /*------------------------------------------------------------------*/
    
    status_ty AddToStr(void *element, int IncrementValue)
    {
        element_ty *p_element = (element_ty *) element;
        char *p_str = (char*) p_element->data;
    
        char *new_str = realloc(p_str, strlen(p_str) + 13);
        if (!new_str) return ...;
    
        sprintf(new_str, "%s%d", p_str, IncrementValue);
    
        p_element->data = new_str;
    
        return ...;
    }
    
    /*------------------------------------------------------------------*/
    
    void PrintInt(void *element)
    {
        element_ty *p_element = (element_ty *) element;
        int *p_value = (int*) &(p_element->data);
        printf("Integer in the element: %d\n", *p_value);
    }
    
    /*------------------------------------------------------------------*/
    
    void PrintFlt(void *element)
    {
        element_ty *p_element = (element_ty *) element;
        float *p_value = (float*) &(p_element->data);
        printf("Float in the element: %f\n", *p_value);
    }
    
    /*------------------------------------------------------------------*/
    
    void PrintStr(void *element)
    {
        element_ty *p_element = (element_ty *) element;
        char *p_str = (char*) p_element->data;
        printf("String in the element: %s\n", p_str);
    }
    
    /*------------------------------------------------------------------*/
    
    status_ty InitInt(int value, element_ty *element)
    {
        int *p_value = (int*) &(element->data);
        *p_value = value;
        element->print = &PrintInt;
        element->add = &AddToInt;
        element->free_element = NULL;
    
        return ...;
    }
    
    /*------------------------------------------------------------------*/
    
    status_ty InitFlt(float value, element_ty *element)
    {
        float *p_value = (float*) &(element->data);
        *p_value = value;
        element->print = &PrintFlt;
        element->add = &AddToFlt;
        element->free_element = NULL;
    
        return ...;
    }
    
    /*------------------------------------------------------------------*/
    
    status_ty InitStr(const char *value, element_ty *element)
    {
        element->data = strdup(value);
        element->print = &PrintStr;
        element->add = &AddToStr;
        element->free_element = &FreeStrElement;
    
        return ...;
    }
    
    /*------------------------------------------------------------------*/
    

    Then you can do things like this:

    void add (int value, element_ty *elements, int n) {
        for(int i = 0 i < n; ++i) {
            element_ty *elem = &elements[i];
            elem->add(elem, value);
        }
    }
    
    void print (element_ty *elements, int n) {
        for(int i = 0 i < n; ++i) {
            element_ty *elem = &elements[i];
            elem->print(elem);
        }
    }
    
    void free_elements (element_ty *elements, int n) {
        for(int i = 0 i < n; ++i) {
            element_ty *elem = &elements[i];
            if (elem->free_element) elem->free_element(elem);
        }
    }
    

    That being said, this approach assumes sizeof(int) and sizeof(float) are <= sizeof(void*), which is usually the case, but not guaranteed.

    This approach also violates Strict Aliasing rules. You can use memcpy() to avoid that, but using a union instead would be easier and cleaner (it also avoids the sizeof issue, too), eg:

    typedef struct
    {
        union {
            int i;
            float f;
            char *s;
        } data;
        void (*print)(void *);
        status_ty (*add)(void *, int);
        void (*free_element)(void *);
    } element_ty;
    
    /*------------------------------------------------------------------*/
    
    void FreeStrElement(void *element)
    {
        element_ty *p_element = (element_ty *) element;
        free(p_element->data.s);
    }
    
    /*------------------------------------------------------------------*/
    
    status_ty AddToInt(void *element, int IncrementValue)
    {
        element_ty *p_element = (element_ty *) element;
        p_element->data.i += IncrementValue;
        return ...;
    }
    
    /*------------------------------------------------------------------*/
    
    status_ty AddToFlt(void *element, int IncrementValue)
    {
        element_ty *p_element = (element_ty *) element;
        p_element->data.f += IncrementValue;
        return ...;
    }
    
    /*------------------------------------------------------------------*/
    
    status_ty AddToStr(void *element, int IncrementValue)
    {
        element_ty *p_element = (element_ty *) element;
        char *p_str = p_element->data.s;
    
        char *new_str = realloc(p_str, strlen(p_str) + 13);
        if (!new_str) return ...;
    
        sprintf(new_str, "%s%d", p_str, IncrementValue);
    
        p_element->data.s = new_str;
    
        return ...;
    }
    
    /*------------------------------------------------------------------*/
    
    void PrintInt(void *element)
    {
        element_ty *p_element = (element_ty *) element;
        printf("Integer in the element: %d\n", p_element->data.i);
    }
    
    /*------------------------------------------------------------------*/
    
    void PrintFlt(void *element)
    {
        element_ty *p_element = (element_ty *) element;
        printf("Float in the element: %f\n", p_element->data.f);
    }
    
    /*------------------------------------------------------------------*/
    
    void PrintStr(void *element)
    {
        element_ty *p_element = (element_ty *) element;
        printf("String in the element: %s\n", p_element->data.s);
    }
    
    /*------------------------------------------------------------------*/
    
    status_ty InitInt(int value, element_ty *element)
    {
        element->data.i = value;
        element->print = &PrintInt;
        element->add = &AddToInt;
        element->free_element = NULL;
    
        return ...;
    }
    
    /*------------------------------------------------------------------*/
    
    status_ty InitFlt(float value, element_ty *element)
    {
        element->data.f = value;
        element->print = &PrintFlt;
        element->add = &AddToFlt;
        element->free_element = NULL;
    
        return ...;
    }
    
    /*------------------------------------------------------------------*/
    
    status_ty InitStr(const char *value, element_ty *element)
    {
        element->data.s = strdup(value);
        element->print = &PrintStr;
        element->add = &AddToStr;
        element->free_element = &FreeStrElement;
    
        return ...;
    }
    
    /*------------------------------------------------------------------*/