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.
Make a temporary variable or buffer to hold the value:
int tmp = 3; append_array(&array, &tmp, 1);
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;
}