Home > Net >  How to properly cast to a void type as an array
How to properly cast to a void type as an array

Time:03-27

I am working on a problem in C where I want to create a dynamic growing array, and if possible utilize the same functions for different data types. Presently I have a struct titled Array that uses a void data type titled *array which is a pointer to the array. It also holds len which stores the active length of the array, size which holds that length of the allocated memory, and elem which stores the length of a datatype that is used to dynamically grow the array.

In addition, I am using three functions. The function initiate_array does the heavy lifting of allocating memory for the array variable in the struct and instantiating all but one of the struct elements. The function init_array acts as a wrapper around initiate_array and also instantiates the variable elem in the struct. Finally, the function append_array adds data/indices to the array and reallocates memory if necessary.

At this point the Array struct, and the functions initiate_array and init_array are independent of data type; however, append_array is hard coded for int variables. I have tried to make append_array somewhat data type independent by making the input int item into void item, but then I get a compile time error at each location with the code ((int *)array->array)[array->len - 1] = item that tells me I cannot cast to a void.

My code is below, does anyone have a suggestion on how I can implement the append_array function to be independent of the datatype of item?

NOTE: I also have a function to free memory at the end of execution, but I am omitting it from this question since it is not relevant.

array.h

#ifndef ARRAY_H
#define ARRAY_H

#include <stdlib.h>
#include <stdio.h>

typedef struct
{
    void *array;
    size_t len;
    size_t size;
    int elem;
} Array;

void initiate_array(Array *array, size_t num_indices);
Array init_array(int size, size_t num_indices);
void append_array(Array *array, int item);

#endif /* ARRAY_H */

array.c

#include "array.h"

void initiate_array(Array *array, size_t num_indices) {
    void *pointer;

    pointer = malloc(num_indices * array->elem);

    if (pointer == NULL) {
        printf("Unable to allocate memory, exiting.\n");
        free(pointer);
        exit(0);
    }
    else {
        array->array = pointer;
        array->len = 0;
        array->size = num_indices;
    }
}

Array init_array(int size, size_t num_indices) {
    Array array;
    array.elem = size;
    initiate_array(&array, num_indices);
    return array;
}

void append_array(Array *array, int item) {
    array->len  ;
    if (array->len == array->size){
        array->size *= 2;
        void *pointer;
        pointer = realloc(array->array, array->size * array->elem);

        if (pointer == NULL) {
            printf("Unable to reallocate memory, exiting.\n");
            free(pointer);
            exit(0);
        }
        else {
            array->array = pointer;
            ((int *)array->array)[array->len - 1] = item;
        }
    }
    else
        ((int *)array->array)[array->len - 1] = item;
}

main.c

#include <stdio.h>
#include <stdlib.h>
#include "array.h"

int main(int argc, char** argv)
{
    int i, j;
    size_t indices = 20;
    Array pointers = int_array(sizeof(int), indices);

    for (i = 0; i < 50; i  )
    {
        append_int_array(&pointers, i);
    }

    for (i = 0; i < pointers.len; i  )
    {
        printf("Value: %d Size:%zu \n",((int *) pointers.array)[i], pointers.len);
    }

    return (EXIT_SUCCESS);
}

CodePudding user response:

I would start by looking at append_array. void is not a complete type, which means that you can not pass in void objects by value. You can pass them by reference though, and void * can refer to any other type as well:

void append_array(Array *array, void *item) {

Right now, you are assuming that you are passing in a single element. But why stop there? You can make your function signature look like this:

void append_array(Array *array, void *items, size_t count) {

The additional caveat here is that array->size * 2 may be insufficient to hold the appended data. You could use (array->len count) * 2 instead.

Assuming that array->size is large enough, you can copy the elements directly using memcpy and a cast to char *, which is guaranteed by the standard to have size-1 elements:

memcpy((char *)array->array   array->len * array->elem, items, count * array->elem);

Notice that I used array->len for the index here. That is because my next suggestion is to increment array->len only after you make the copy. That would make your size check simpler, and not subject to reallocation with one element to spare, as you have now. Remember that array->len is not only the size of the array, it is also the zero based index that you want to append to.

if (array->len   count > array->size) {

For a single element, the condition would be

if (array->len >= array->size) {

Finally, I strongly suggest you return an integer error code instead of exiting. The user of this function should expect to be able to do cleanup at the very least in case of a memory error, or possibly free up cache elements, not unilaterally crash.

Here is what the final function would look like:

int append_array(Array *array, void *items, size_t count)
{
    if (array->len   count > array->size) {
        size_t size = (array->len   count) * 2;
        void *pointer = realloc(array->array, size * array->elem);
        if (pointer == NULL) {
            return 0;
        }
        array->array = pointer;
        array->size = size;
    }
    memcpy((char *)array->array   array->len * array->elem, items, count * array->elem);
    array->len  = count;
    return 1;
}

Writing the function this way has one slight drawback: since rvalues don't have an address, you can't call

append_array(&array, &3, 1);

You can work around this in two ways.

  1. Make a temporary variable or buffer to hold the value:

    int tmp = 3;
    append_array(&array, &tmp, 1);
    
  2. Make a type-specific wrapper that can accept elements for complete types. This works because C is purely pass-by-value (i.e., copy), so you can do

    int append_int(Array *array, int value)
    {
        return append_array(array, &value, 1);
    }
    

    In this case, you are effectively using a new stack frame to hold the value of tmp in the first example.

CodePudding user response:

The type void is an incomplete type, and one which cannot be completed, so you can't assign to or from it, or use it as an array parameter type.

What you can do is change append_array to take a void * as an argument which points to the data to be added. Then you convert your data pointer to char * so you can do single byte pointer arithmetic to get to the correct offset, then use memcpy to copy in the data.

void append_array(Array *array, void *item) {
    array->len  ;
    if (array->len == array->size){
        array->size *= 2;
        void *pointer;
        pointer = realloc(array->array, array->size * array->elem);

        if (pointer == NULL) {
            printf("Unable to reallocate memory, exiting.\n");
            free(array->array);
            exit(0);
        }
        else {
            array->array = pointer;
        }
    }
    char *p = (char *)array->array   (array->len - 1) * array->elem;
    memcpy(p, item, array->elem);
}

You won't be able to call this function by passing an integer literal to add, but you can use the address of a compound literal.

append_array(array, &(int){ 3 });

CodePudding user response:

It should be something like this, or you could use typeof to improve it. Or, use void **array;

#include <assert.h>
#include <memory.h>
#include <stdio.h>
#include <stdlib.h>

typedef struct {
    void *array;
    size_t size;
    size_t capacity;
    int elem_size;
} Array;

void initiate_array(Array *array, size_t num_indices);
Array init_array(int size, size_t num_indices);
void append_array(Array *array, void *item);

void initiate_array(Array *array, size_t num_indices) {
    void *pointer;

    pointer = malloc(num_indices * array->elem_size);

    if (pointer == NULL) {
        printf("Unable to allocate memory, exiting.\n");
        // free(pointer);
        exit(0);
    } else {
        array->array = pointer;
        array->size = 0;
        array->capacity = num_indices;
    }
}

Array init_array(int elem_size, size_t num_indices) {
    Array array;
    array.elem_size = elem_size;
    initiate_array(&array, num_indices);
    return array;
}

void append_array(Array *array, void *item) {
    if (array->size == array->capacity) {
        // extend the array
    }

    memcpy(array->array   array->size * array->elem_size, item,
           array->elem_size);
    array->size  ;
}

int main(void) {
    Array arr = init_array(sizeof(int), 10);

    int item = 1;
    append_array(&arr, &item);
    item = 2;
    append_array(&arr, &item);
    item = 3;
    append_array(&arr, &item);

    return 0;
}
  • Related