Related
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 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 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);
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;
}
In this thread I was suggested to use max_align_t in order to get an address properly aligned for any type, I end up creating this implementation of a dynamic array:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
struct vector {
size_t capacity;
size_t typesize;
size_t size;
max_align_t data[];
};
#define VECTOR(v) ((struct vector *)((unsigned char *)v - offsetof(struct vector, data)))
static void *valloc(size_t typesize, size_t size)
{
struct vector *vector;
vector = calloc(1, sizeof(*vector) + typesize * size);
if (vector == NULL) {
return NULL;
}
vector->typesize = typesize;
vector->capacity = size;
vector->size = 0;
return vector->data;
}
static void vfree(void *data, void (*func)(void *))
{
struct vector *vector = VECTOR(data);
if (func != NULL) {
for (size_t iter = 0; iter < vector->size; iter++) {
func((unsigned char *)vector->data + vector->typesize * iter);
}
}
free(vector);
}
static void *vadd(void *data)
{
struct vector *vector = VECTOR(data);
struct vector *new;
size_t capacity;
if (vector->size >= vector->capacity) {
capacity = vector->capacity * 2;
new = realloc(vector, sizeof(*vector) + vector->typesize * capacity);
if (new == NULL) {
return NULL;
}
new->capacity = capacity;
new->size++;
return new->data;
}
vector->size++;
return vector->data;
}
static size_t vsize(void *data)
{
return VECTOR(data)->size;
}
static void vsort(void *data, int (*comp)(const void *, const void *))
{
struct vector *vector = VECTOR(data);
if (vector->size > 1) {
qsort(vector->data, vector->size, vector->typesize, comp);
}
}
static char *vgetline(FILE *file)
{
char *data = valloc(sizeof(char), 32);
size_t i = 0;
int c;
while (((c = fgetc(file)) != '\n') && (c != EOF)) {
data = vadd(data);
data[i++] = (char)c;
}
data = vadd(data);
data[i] = '\0';
return data;
}
struct data {
int key;
char *value;
};
static int comp_data(const void *pa, const void *pb)
{
const struct data *a = pa;
const struct data *b = pb;
return strcmp(a->value, b->value);
}
static void free_data(void *ptr)
{
struct data *data = ptr;
vfree(data->value, NULL);
}
int main(void)
{
struct data *data;
data = valloc(sizeof(struct data), 1);
if (data == NULL) {
perror("valloc");
exit(EXIT_FAILURE);
}
for (size_t i = 0; i < 5; i++) {
data = vadd(data);
if (data == NULL) {
perror("vadd");
exit(EXIT_FAILURE);
}
data[i].value = vgetline(stdin);
data[i].key = (int)vsize(data[i].value);
}
vsort(data, comp_data);
for (size_t i = 0; i < vsize(data); i++) {
printf("%d %s\n", data[i].key, data[i].value);
}
vfree(data, free_data);
return 0;
}
But I'm not sure if I can use max_align_t to store a chunk of bytes:
struct vector {
size_t capacity;
size_t typesize;
size_t size;
max_align_t data[]; // Used to store any array,
// for example an array of 127 chars
};
Does it break the one past the last element of an array rule?
Does it break the one past the last element of an array rule?
No.
Using max_align_t to store a chunk of bytes
OP's issue is not special because it uses a flexible array member.
As a special case, the last element of a structure ... have an incomplete array type; this is called a flexible array member. ... However, when a . (or ->) operator has a left operand that is (a pointer to) a structure with a flexible array member and the right operand names that member, it behaves as if that member were replaced with the longest array (with the same element type) ...
It is the same issue as accessing any allocated memory or array of one type as if it was another type.
The conversion from max_align_t * to char * to void * is well defined when alignment is done right.
A pointer to an object type may be converted to a pointer to a different object type. If the resulting pointer is not correctly aligned for the referenced type, the behavior is undefined. C11dr ยง6.3.2.3 7
All reviewed accessing in code do not attempt to access outside the "as if" array.