I am trying to produce a library that contains C functions to create a dynamically allocated array as well as other basic array operations. I have defined a typedef struct that contains a pointer variable array which stores the data points in the array. The struct also contains other variables such as the allocated array size the array length (i.e. len) as well as the array name and datatype. The goal is that the array should be dynamically allocated and the array container should be able to hold any data type. I have created a function titled init_array, which is a wrapper around array_mem_alloc to instantiate the array container. Finally I have another function titled append_array where the user can pass a scalar or another defined array that will be appended to the data already within array.
I am trying to create a function titled pop_array, but I am struggling with how to write it. Normally you could just iterate over a for loop from indice to the assigned length, overwrite the first indice and move all others to the left. Unfortunately in this case array is a void variable, and I run into problems assigning data to a void. I have tried some implementations with memcp and memmove, but I can not find a solution where the compiler allows me to assign the data to a void type. Any thoughts or help would be appreciated. The code is shown below.
array.h
#ifndef ARRAY_H
#define ARRAY_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct
{
void *array; // Pointer to array
size_t len; // Active length of array
size_t size; // Number of allocated indizes
int elem; // Memory consumption per indice
char *name; // The array name
char *dtype; // A string representing the datatype
} Array;
void array_mem_alloc(Array *array, size_t num_indices);
Array init_array(char *dtype, size_t num_indices, char *name);
int append_array(Array *array, void *elements, size_t count);
int pop_array(Array *array, int indice);
Array.c
void array_mem_alloc(Array *array, size_t num_indices) {
// Determine the total memory allocation and assign to pointer
void *pointer;
pointer = malloc(num_indices * array->elem);
// If memory is full fail gracefully
if (pointer == NULL) {
printf("Unable to allocate memory, exiting.\n");
free(pointer);
exit(0);
}
// Allocate resources and instantiate Array
else {
array->array = pointer;
array->len = 0;
array->size = num_indices;
}
}
// --------------------------------------------------------------------------------
Array init_array(char *dtype, size_t num_indices, char *name) {
// Determine memory blocks based on data type
int size;
if (strcmp(dtype, "float") == 0) size = sizeof(float);
else if (strcmp(dtype, "int") == 0) size = sizeof(int);
else if (strcmp(dtype, "double") == 0) size = sizeof(double);
else if (strcmp(dtype, "char") == 0) size = sizeof(char);
else {
printf("Data type not correctly entered into init_array, exiting program!\n");
exit(0);
}
// Allocate indice size and call array_mem_alloc
Array array;
array.dtype = dtype;
array.elem = size;
array_mem_alloc(&array, num_indices);
array.name = name;
return array;
}
// --------------------------------------------------------------------------------
int append_array(Array *array, void *elements, size_t count) {
// Allocae more memory if necessary
if (array->len + count > array->size) {
size_t size = (array->len + count) * 2;
void *pointer = realloc(array->array, size * array->elem);
// If memory is full return operations
if (pointer == NULL) {
printf("Unable to allocate memory, exiting.\n");
return 0;
}
// Allocate memory to variables and increment array size
array->array = pointer;
array->size = size;
}
// Append variables and increment the array length
memcpy((char *)array->array + array->len * array->elem, elements, count * array->elem);
array->len += count;
return 1;
}
// --------------------------------------------------------------------------------
int pop_array(Array *array, int indice) {
if (indice >= array->len) {
printf("Indice %d out of bounds for pop_array", indice);
return 0;
}
for (int i = index; i < array->len; i++) {
// This does not work because I cannot assign to a void type
// - I have tried to several solutions with append_array and memcpy
// but all solutions seem to run into a problem where assigning data
// already in the array is not possible.
array->array[i] = array->array[i+1];
}
// Decrement array length
array->len -= 1;
return 1;
}
main.c
int main(int argc, const char * argv[]) {
size_t indices = 10;
char name[6] = "array";
char dtype[4] = "int";
Array arr_test = init_array(dtype, indices, name);
int a[3] = {10, 9, 8};
append_array(&arr_test, &a, 3);
// pop array function call here
}
Belaying potential alignment issues, you don't need a loop. I believe this is all you need to shift the right-most desired elements 'back' one 'slot':
unsigned char *dst = (unsigned char*)array->array + indice * array->elem;
memmove(dst, dst + array->elem, array->elem * (array->len - indice - 1));
There are lot of things that go wrong in your implementation.
For example, the way you use your name char pointer can lead to it pointing to deallocated memory.
The following code :
void foo(Array *array) {
char name[] = "abc";
char type[] = "int";
init_array(type, name, array);
}
int main(int args, char **argv) {
Array array;
foo(&array);
puts(array.name);
}
leads to undefined behavior, because array.name and array.dtype point to chunks of memory which have been popped from the stack.
One can also mention that you only manage very few cases. What happens if the user input "short" as dtype ?
For your specific problem, memmove is your friend :
void* slot = array->array + array->elem * indice;
memcpy(array->array + array->len * array->elem, slot, array->elem);
--vector->len;
memmove(slot, (const void *) slot + array->elem, (array->len - indice) * array->elem);
Related
I am working on a basic framework to dynamically allocate array with the C language. I have created a function to create an array of strings titled init_string_vector. Data can be appended to the array with the append_string_vector function and data can be de-allocated from the heap with the free_string_array function. I am currently working on a function titled replace_string_vector_index that allows a user to pass an array index to the function as well as a pointer to the string array. If the array is typed as a STRING array and the index is not out of bounds, the function should replace the existing data with the string that a user passes to the function.
The replace_string_vector_index function appears to work properly and does replace the string at the index with the other string the user passed to the function. However, the free_string_array function no longer works once I have used to replace_string_vector_index function to act on the array. This makes me think that the process within the function is causing an issue, but I cannot see how. An example is shown below. When the free_string_array function fails, I get the following error, free(): invalid pointer.
vector.h
#ifndef ARRAY_H
#define ARRAY_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
typedef enum
{
FLOAT,
DOUBLE,
CHAR,
INT,
STRING
} dat_type;
// --------------------------------------------------------------------------------
typedef struct
{
char **array;
size_t len;
int elem;
dat_type dat;
} StringVector;
// --------------------------------------------------------------------------------
int string_vector_mem_alloc(StringVector *array, size_t num_indices);
// --------------------------------------------------------------------------------
StringVector init_string_vector();
// --------------------------------------------------------------------------------
int append_string_vector(StringVector *s, char *value);
// --------------------------------------------------------------------------------
void free_string_array(StringVector *array);
// --------------------------------------------------------------------------------
int replace_string_vector_index(StringVector *array, int index, char string[]);
// --------------------------------------------------------------------------------
vector.c
#include "vector.h"
int string_vector_mem_alloc(StringVector *array, size_t num_indices) {
// Determine the total memory allocation and assign to pointer
void *pointer;
pointer = malloc(num_indices * array->elem);
// If memory is full fail gracefully
if (pointer == NULL) {
printf("Unable to allocate memory, exiting.\n");
free(pointer);
return 0;
}
// Allocate resources and instantiate Array
else {
array->array = pointer;
array->len = 0;
return 1;
}
}
// --------------------------------------------------------------------------------
StringVector init_string_vector() {
StringVector array;
array.dat = STRING;
array.elem = sizeof(char *);
string_vector_mem_alloc(&array, array.elem);
return array;
}
// --------------------------------------------------------------------------------
int append_string_vector(StringVector *array, char *value) {
value = strdup(value);
if (!value) {
return -1;
}
array->len++;
char **resized = realloc(array->array, sizeof(char *)*array->len + 1);
if (!resized) {
free(value);
return -1;
}
resized[array->len-1] = value;
array->array = resized;
return 0;
}
// --------------------------------------------------------------------------------
void free_string_array(StringVector *array) {
if (array != NULL) {
for (int i = 0; i < array->len; i++) {
free(array->array[i]);
}
}
free(array->array);
// Reset all variables in the struct
array->array = NULL;
array->len = 0;
array->elem = 0;
}
// --------------------------------------------------------------------------------
int replace_string_vector_index(StringVector *array, int index, char string[]) {
if (array->dat != STRING) {
printf("Array data type must be a STRING");
return 0;
}
if (index > array->len) {
printf("Index is greater than array length");
return 0;
}
* (char **) ((char *) array->array + index * array->elem) = string;
return 1;
}
// --------------------------------------------------------------------------------
main.c
#include <stdio.h>
#include "vector.h"
int main(int argc, const char * argv[]) {
StringVector arr_test = init_string_vector();
char one[] = "Hello";
char two[] = "World";
char three[] = "Hello";
char four[] = "Goodbye";
append_string_vector(&arr_test, one);
append_string_vector(&arr_test, two);
append_string_vector(&arr_test, three);
append_string_vector(&arr_test, four);
// I can free the array at this point
free_string_array(&arr_test)
StringVector arr_test = init_string_vector();
append_string_vector(&arr_test, one);
append_string_vector(&arr_test, two);
append_string_vector(&arr_test, three);
append_string_vector(&arr_test, four);
replace_string_vector_index(&arr_test, 1, one);
// - Once I envoke replace_string_vector_index, free_string_array
// no longer works, and I get an invalid pointer error.
free_string_array(&arr_test);
}
If I understand the requirements for your replace_string_vector_index function, you should first free the memory of array->array[index], then assign the result of strdup(string) to that element.
No casting needed, no complex pointer arithmetic. Just simply:
free(array->array[index]);
array->array[index] = strdup(string);
What happens now (I think) is that you make array->array[index] point to the array that contains the string (i.e. you forget the strdup step). An array that wasn't allocated by malloc, and which can't be passed to free.
Since you will pass it to free as part of free_string_array you will have undefined behavior.
I want to get the i-th element of the void*. I understand that it is void type and I have to give it a certain data type. The idea behind this is that it should be working for different data types. If i am right about the issue, how do I implement that?
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#define MAX_STR_LEN 64
typedef struct Vector {
void *data;
size_t element_size;
size_t size;
size_t capacity;
} Vector;
// Allocate vector to initial capacity (block_size elements),
// Set element_size, size (to 0), capacity
void init_vector(Vector *vector, size_t block_size, size_t element_size){
vector->data = (void*) malloc(block_size * element_size); // i am questioning whether this is correct
vector->element_size = element_size;
vector->size = 0;
vector->capacity = block_size * element_size;
}
void resize(Vector *vector, size_t new_size){
void *data2 = (void*) malloc(new_size * vector->element_size);
int i=0;
memmove(data2,vector->data,new_size * vector->element_size);
vector->data = data2;
if(new_size > vector->size){
for(i=vector->size-1;i<new_size;i++){
vector->data[i]=0; // here is the problem
}
}else{
vector->size = new_size;
}
vector->capacity = new_size*vector->element_size;
}
C is a language for adrenaline junkies. It's always akin to free climbing, sky diving and formula 1 racing.
Defensive programming is not a perfect protection against all kinds of "sabotage" by a caller, but it is better than nothing.
The code below is in that very spirit. It cannot detect all possible things going wrong (like corrupted memory or if the data pointer in the vector is just a random value), but it showcases the least amount of defensive programming one should use if writing in that language.
C has "pointer arithmetic" as a feature. So, if you have e.g. a uint16_t * p = 1000; and you access p + 1, it is accessing 1002 (i.e. p + sizeof(uint16_t) * 1).
And this is the trick, how you can access an element in such a very weakly typed vector. You cast the void pointer to a byte pointer (as an example) and then use pointer arithmetic.
void* at(Vector* v, size_t index) {
// C needs manual sanity checks for the preconditions
if (NULL == v) return NULL;
if (v->size <= index) return NULL;
if (NULL == v->data) return NULL;
// now we can be (reasonably, as much as we can tell) sure, we did not get garbage as arguments...
return ((char*)(v->data)) + index * v->element_size;
}
Your vector functions can not [meaningfully] access the array data using the index or pointer syntax such as:
vector->data[i]=0;
That is because it's a void * pointer but, more importantly, if element_size is (e.g.) 8, does that mean vector->data points to a double or unsigned long long?
Only the caller of your functions can do this:
Vector *vec = calloc(1,sizeof(*vec));
init_vector(vec,100,sizeof(double));
double *ptr = vec->data;
for (size_t idx = 0; idx < vec->size; ++idx)
ptr[idx] = idx;
When you extend the array in resize, you can only mem* functions.
Replace your for loop with:
memset(&vector->data[vector->size * vector->element_size],0,
(new_size - vector->size) * vector->element_size);
UPDATE:
There are some more issues. Although you can have capacity be a byte count, it is more usual for it to be an element count (just like size).
When I create such dynamic array/vector objects/functions, I usually do not have resize do initialization of the elements.
That's because it doesn't [really] know how to initialize the elements. For a c++ vector, the constructor knows.
So, if we wish resize [and init_vector] to do this, we need to provide a function pointer for this.
Here's some refactored code to illustrate:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#define MAX_STR_LEN 64
typedef struct Vector Vector;
typedef void (*vecinit_p)(Vector *,size_t idx,size_t count);
struct Vector {
void *data; // pointer to array data
size_t element_size; // number of bytes in an array element
size_t capacity; // number of elements allocated
size_t size; // number of elements in use
vecinit_p initfnc; // pointer to init function
};
// vecptr -- get pointer to i'th element
void *
vecptr(Vector *vec,size_t idx)
// idx -- index of desired element
{
void *ptr;
idx *= vec->element_size;
ptr = vec->data;
ptr += idx;
return ptr;
}
// init_data -- initialize data elements
void
init_data(Vector *vec,size_t idx,size_t count)
// idx -- starting index
// count -- number of elements to initialize
{
void *ptr = vecptr(vec,idx + 0);
void *end = vecptr(vec,idx + count);
memset(ptr,0,end - ptr);
}
// Allocate vector to initial capacity (block_size elements),
// Set element_size, size (to 0), capacity
void
init_vector(Vector *vec, size_t block_size, size_t element_size,vecinit_p fnc)
// block_size -- number of elements
// element_size -- number of bytes in a single element
{
size_t new_len = block_size * element_size;
vec->data = calloc(1,new_len);
vec->element_size = element_size;
vec->size = 0;
vec->capacity = block_size;
// provide a "default" constructor
if (fnc == NULL)
fnc = init_data;
vec->initfnc = fnc;
fnc(vec,0,vec->capacity);
}
// resize -- resize the array
void
resize(Vector *vec, size_t new_cap)
// new_cap -- desired new capacity
{
// get byte length
size_t new_len = new_cap * vec->element_size;
void *data2 = malloc(new_len);
if (data2 == NULL) {
perror("malloc");
exit(1);
}
vec->data = data2;
// get old capacity and set new capacity
size_t old_cap = vec->capacity;
vec->capacity = new_cap;
// initialize new elements
if (new_cap > old_cap)
vec->initfnc(vec,old_cap,old_cap - new_cap);
}
// vecpush -- append element to array
// RETURNS: pointer to "pushed" element
void *
vecpush(Vector *vec)
{
// increase array capacity if needed
if (vec->size >= vec->capacity)
resize(vec,vec->capacity + 10);
// point to element
void *ptr = vecptr(vec,vec->size++);
return ptr;
}
I have been working to create a set of functions that allow for the creation and manipulation of a dynamically allocated array in C for any data type. I have created several functions, but of most relevance to this post are the following functions;
vector_mem_alloc This function is not called directly, but when indirectly called in a wrapper it allocates memory for the array based on the data type
init_vector This function is called directly by a user and is a wrapper around vector_mem_alloc. This function preps data and instantiates certain parameters in the Vector struct.
append_vector This function allows a user to append the 1-D array with scalar values or another already created array.
The code for these working functions is shown below.
vector.h
#ifndef ARRAY_H
#define ARRAY_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
typedef enum
{
FLOAT,
DOUBLE,
CHAR,
INT
} dat_type;
typedef struct
{
void *array; // Pointer to array
size_t len; // Active length of array
size_t size; // Number of allocated indizes
int elem; // Memory consumption per indice
char name[20]; // The array name
dat_type dat;
} Vector;
// --------------------------------------------------------------------------------
void vector_mem_alloc(Vector *array, size_t num_indices);
// --------------------------------------------------------------------------------
Vector init_vector(dat_type dat, size_t num_indices, char *name);
// --------------------------------------------------------------------------------
int append_vector(Vector *array, void *elements, size_t count);
#endif /* ARRAY_H */
vector.c
#include "vector.h"
// Begin code
void vector_mem_alloc(Vector *array, size_t num_indices) {
// Determine the total memory allocation and assign to pointer
void *pointer;
pointer = malloc(num_indices * array->elem);
// If memory is full fail gracefully
if (pointer == NULL) {
printf("Unable to allocate memory, exiting.\n");
free(pointer);
exit(0);
}
// Allocate resources and instantiate Array
else {
array->array = pointer;
array->len = 0;
array->size = num_indices;
}
}
// --------------------------------------------------------------------------------
Vector init_vector(dat_type dat, size_t num_indices, char *name) {
// Determine memory blocks based on data type
int size;
switch(dat) {
case FLOAT:
size = sizeof(float);
break;
case INT:
size = sizeof(int);
break;
case DOUBLE:
size = sizeof(double);
break;
case CHAR:
size = sizeof(char);
break;
default:
printf("Data type not correctly entered, instantiating int array!\n");
size = sizeof(int);
dat = INT;
}
// Allocate indice size and call array_mem_alloc
Vector array;
array.dat = dat;
array.elem = size;
vector_mem_alloc(&array, num_indices);
strncpy(array.name, name, sizeof(array.name));
return array;
}
// --------------------------------------------------------------------------------
int append_vector(Vector *array, void *elements, size_t count) {
// Allocae more memory if necessary
if (array->len + count > array->size) {
size_t size = (array->len + count) * 2;
void *pointer = realloc(array->array, size * array->elem);
// If memory is full return operations
if (pointer == NULL) {
printf("Unable to allocate memory, exiting.\n");
return 0;
}
// Allocate memory to variables and increment array size
array->array = pointer;
array->size = size;
}
// Append variables and increment the array length
memcpy((char *)array->array + array->len * array->elem, elements, count * array->elem);
array->len += count;
return 1;
}
main.c
// - This shows an implementation for an integer array, but it works
// for FLOAT, DOUBLE, and CHAR as well.
size_t indices = 10;
char name[6] = "array";
dat_type dtype = INT;
Vector arr_test = init_vector(dtype, indices, name);
int a[3] = {10, 9, 8};
append_vector(&arr_test, &a, 3);
Everything listed above works just fine. However, I am trying to expand the capability of the above code to cover multi-arrays of any data type; however, in the near term I am particularly interested in string arrays. I am trying to add another struct to the vector.h file that references marray[] as a data type of Vector. In the long run, I hope to be able to reference the name of each array in the multi-array and treat it similar to a dictionary. I have tried several function that might allow an interface like shown below, but so far none of them work. Does anyone have any suggestions that I might be able to use as a starting point in building this functionality. My intended addition to the vector.h file is shown below with a main.c implentation that shows how I hope to interface with this.
typedef struct
{
size_t len;
size_t size;
int elem;
dat_type dat;
Vector *marray[];
} MVector;
size_t indices = 10;
dat_type dtype = INT;
MVector arr_test = init_vector(dtype, indices);
int a[3] = {10, 9, 8};
append_vector(&arr_test[0], &a, 3);
int b = 3;
append_vector(&arr_test[1], &b, 1);
append_vector(&arr_test[1], &b, 1);
b = 4;
append_vector(&arr_test[1], &b, 1)
append_vector(&arr_test[1], &b, 1);
// result [[10, 9, 8], [3, 3, 4, 4]]
I have been slowly working on a library that would allow a user to dynamically build an array in the C language. Of the many functions I have written, I have developed a solution to replace the value at a user defined index with another value, but I am not sure it is a very efficient solution and would like any thoughts if someone might have a better method that does nto require creating several intermediate arrays to store data.
I am using a typedef struct titled Array which acts as a container for the pointer variable array, as well as len that contains the active length of the array, size which contains the allocated size of the array, elem which contains the memory allocation per indice, which can change depending on the data type chosen by the user, name which is a character string the user can assign to each array instantiation, and dat_type which references an enum to describe the data type the array was instantiated with.
So far I have built several functions to work in conjunction with the Array container, of which the most relevant for this question are the following functions;
type_dat
An enum with variables for float, double, char, and int.
Array a struct that acts as a container for an array.
array_mem_alloc
The function allocates memory for the array and should not be called directly.
init_array
This function acts as a wrapper around array_mem_alloc and is how the user should instantiate an array. This returns an Array container.
append_array This allows a user to append a scalar or an array to the array within the Array container.
preappend_array This function allows a user to ad a scalar or an array to the beginning of an array within the Array container.
int_array_val This function retrieves integer variables from the array. There is a version of this function for each data type; however, this specific function is relevant to the example below.
replace_int_array_indice This function allows a user to enter a specific indices and replace the variable at that indice with another integer value. I have coded a solution for this function, but I do not believe it is an efficient implementation. In the below example, the implementation requires that two intermediate arrays be created to store data before the memmove and memcpy function can bring all of the data together. I am hoping that someone can tell me if there is a better solution for this function then the one I am providing below.
My files are shown below
array.h
#ifndef ARRAY_H
#define ARRAY_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef enum
{
FLOAT,
DOUBLE,
CHAR,
INT
} dat_type;
// --------------------------------------------------------------------------------
typedef struct
{
void *array; // Pointer to array
size_t len; // Active length of array
size_t size; // Number of allocated indizes
int elem; // Memory consumption per indice
char name[20]; // The array name
dat_type dat;
} Array;
// --------------------------------------------------------------------------------
void array_mem_alloc(Array *array, size_t num_indices);
// --------------------------------------------------------------------------------
Array init_array(dat_type dat, size_t num_indices, char *name);
// --------------------------------------------------------------------------------
int append_array(Array *array, void *elements, size_t count);
// --------------------------------------------------------------------------------
int int_array_val(Array *array, int indice);
// --------------------------------------------------------------------------------
int preappend_array(Array *array, void *elements, size_t count);
// --------------------------------------------------------------------------------
void replace_int_array_indice(Array *array, int index, int replacement_value);
#endif /* ARRAY_H */
Array.c
void array_mem_alloc(Array *array, size_t num_indices) {
// Determine the total memory allocation and assign to pointer
void *pointer;
pointer = malloc(num_indices * array->elem);
// If memory is full fail gracefully
if (pointer == NULL) {
printf("Unable to allocate memory, exiting.\n");
free(pointer);
exit(0);
}
// Allocate resources and instantiate Array
else {
array->array = pointer;
array->len = 0;
array->size = num_indices;
}
}
// --------------------------------------------------------------------------------
Array init_array(dat_type dat, size_t num_indices, char *name) {
// Determine memory blocks based on data type
int size;
switch(dat) {
case FLOAT:
size = sizeof(float);
break;
case INT:
size = sizeof(int);
break;
case DOUBLE:
size = sizeof(double);
break;
case CHAR:
size = sizeof(char);
break;
default:
printf("Data type not correctly entered, instantiating int array!\n");
size = sizeof(int);
dat = INT;
}
// Allocate indice size and call array_mem_alloc
Array array;
array.dat = dat;
array.elem = size;
array_mem_alloc(&array, num_indices);
strcpy(array.name, name);
return array;
}
// --------------------------------------------------------------------------------
int append_array(Array *array, void *elements, size_t count) {
// Allocae more memory if necessary
if (array->len + count > array->size) {
size_t size = (array->len + count) * 2;
void *pointer = realloc(array->array, size * array->elem);
// If memory is full return operations
if (pointer == NULL) {
printf("Unable to allocate memory, exiting.\n");
return 0;
}
// Allocate memory to variables and increment array size
array->array = pointer;
array->size = size;
}
// Append variables and increment the array length
memcpy((char *)array->array + array->len * array->elem, elements, count * array->elem);
array->len += count;
return 1;
}
// --------------------------------------------------------------------------------
int preappend_array(Array *array, void *elements, size_t count) {
// Allocae more memory if necessary
if (array->len + count > array->size) {
size_t size = (array->len + count) * 2;
void *pointer = realloc(array->array, size * array->elem);
// If memory is full return operations
if (pointer == NULL) {
printf("Unable to allocate memory, exiting.\n");
exit(0);
}
// Allocate memory to variables and increment array size
array->array = pointer;
array->size = size;
}
// Preappend variables and increment the array length
memmove(
((char *) array->array) + count * array->elem,
array->array,
array->len * array->elem);
memcpy(array->array, elements, count * array->elem);
array->len += count;
return 1;
}
// --------------------------------------------------------------------------------
void replace_int_array_indice(Array *array, int index, int replacement_value) {
// THIS FUNCTION WORKS, BUT I DO NOT THINK IT IS AN EFFICIENT IMPLEMENTATION
// Copy data to intermediate array
int arr[array->len];
memcpy(arr, array->array, array->elem * array->len);
memmove(((char *) array->array), arr + index + 1, array->len * array->elem);
array->len -= index + 1;
// preappend with replacement value
preappend_array(array, &replacement_value, 1);
// preappend with initial indices up to replaced index
int new_arr[index - 1];
memcpy(new_arr, arr, index * array-> elem);
preappend_array(array, &new_arr, index);
}
main.c
#include "array.h"
int main(int argc, const char * argc[]) {
size_t indices = 10;
char name[6] = "array";
dat_type dtype = INT;
Array arr_test = init_array(dtype, indices, name);
int a[6] = {1, 2, 3, 4, 5, 6};
append_array(&arr_test, a, 6);
replace_int_array_indice(&arr_test, 1, 5);
for (int j = 0; j < array_test.len; j++) {
printf("%d\n", int_array_val(&arr_test, j);
}
}
You are definitely overthinking this one.
You only need to replace one value in the array, so find the offset, cast correctly, dereference and assign:
* (int *) ((char *) array->array + index * array->elem) = replacement_value;
Or, continue to work with memcpy when building base functionality, and use that to create further abstractions.
void array_set(Array *array, size_t index, void *data) {
memcpy(
(char *) array->array + index * array->elem,
data,
array->elem);
}
void int_array_set(Array *array, size_t index, int value)
{
array_set(array, index, &value);
}
Might want to check that index is valid.
btw, you have a memory corruption vulnerability in your code. in init_array you are using strcpy(array.name, array);, when name is only 20 bytes long. and name is user controled. you should use strncpy(array.name, array, sizeof(array.name); instead.
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);
}
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.
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 });
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;
}