so I have this code, goal is to have a void *data
pointer, which is sometimes used to store a simple int
, sometimes a single char
array and sometimes I need to store an array of char arrays.
I'm making sure that I always know what type of data I have stored in the void pointer.
The code executes well in an online parser and this is the output of it:
sizeof 2 x char*: 8 str0: string1 addr: 2995278544 str1: bla2 addr: 2995278576 checking strings: str0: string1 addr: 2995278544 str1: bla2 addr: 2995278576
The plan was to malloc space for n char* pointers and save that pointer do void *data. Then change the type to "char **ptr" (pointer to a pointer), so I can save the addresses which strdup returns to that array and access them later. checkItems(uint8_t) does exactly that, it re-accesses the "void *data" pointer by changing it to "char **ptr" again to be able to access the memory addresses where the actual C strings are saved.
is this all correct? would one do this differently? should I use some kind of cast to the void *data pointer instead simply saying "char **ptr = data;"?
Thanks!
#include<stdio.h>
#include<stdint.h>
#include<string.h>
#include<stdlib.h>
#include<stdarg.h>
void copyItems(uint8_t num, ...);
void checkItems(uint8_t num);
void *data;
int main()
{
copyItems(2, "string1", "bla2");
checkItems(2);
}
void copyItems(uint8_t num, ...)
{
printf("sizeof %u x char*: %u\r\n", num, sizeof(char*), sizeof(char*)*num);
data = malloc(sizeof(char*)*num);
char **ptr = data;
va_list ap;
va_start(ap, num);
for (uint8_t n = 0; n < num; n )
{
ptr[n] = strdup(va_arg(ap, char*));
printf("str%u: %s addr: %u\r\n", n, ptr[n], ptr[n]);
}
va_end(ap);
}
void checkItems(uint8_t num)
{
char **ptr = data;
printf("checking strings:\r\n");
for (uint8_t n = 0; n < num; n )
{
printf("str%u: %s addr: %u\r\n", n, ptr[n], ptr[n]);
}
}
CodePudding user response:
I would store size and type information in the structure
typedef enum
{
INT,
CHAR,
CHARPTR,
}DATA_TYPE;
typedef struct
{
DATA_TYPE dtype;
size_t size;
void *data;
}DATA;
DATA *copyItems(size_t num, DATA_TYPE type, ...);
void PrintItems(const DATA *data);
size_t datasize(const DATA *data)
{
size_t result;
switch(data -> dtype)
{
case INT:
result = sizeof(int);
break;
case CHAR:
result = 1;
break;
case CHARPTR:
result = sizeof(char *);
break;
default:
result = 0;
break;
}
return result;
}
int main()
{
DATA *data = copyItems(2, CHARPTR, "string1", "bla2");
PrintItems(data);
}
DATA *copyItems(size_t num, DATA_TYPE type, ...)
{
DATA *data = malloc(sizeof(*data));
va_list ap;
va_start(ap, type);
if(data)
{
data -> size = 0;
data -> data = malloc(datasize(data) * num);
data -> dtype = type;
if(data -> data)
{
for (size_t n = 0; n < num; n , data -> size )
{
switch(data -> dtype)
{
case INT:
((int *)data -> data)[n] = va_arg(ap, int);
break;
case CHARPTR:
((char **)data -> data)[n] = strdup(va_arg(ap, char *));
break;
default:
break;
}
}
}
else
{ /* error handler */}
}
va_end(ap);
return data;
}
void PrintItems(const DATA *data)
{
if(data && data -> size && data -> data)
{
for(size_t i = 0; i < data -> size; i )
{
switch(data -> dtype)
{
case INT:
printf("[%zu] = %d\n", i, ((int *)data -> data)[i]);
break;
case CHARPTR:
printf("[%zu] = %s\n", i, ((char **)data -> data)[i]);
break;
default:
break;
}
}
}
}
https://godbolt.org/z/PjWhrGvvP
CodePudding user response:
This is a sort of variant record, present in C and many languages like Pascal, and maybe you could stick to the usual.
In C
we can use anonymous unions to this effect.
I will show you a short example.
Note that I will not implement here the logic to copy the strings to a new vector, and it is important for safety. Please comment if you think you need an example.
The data structure example
typedef struct
{
unsigned char id; // 1,2,3
union
{
unsigned char the_char;
unsigned char size; // up to 255 strings
};
union
{
int the_int;
const char** argv;
};
} Data;
The reason for the 1st union is described below, but it also helps to keep the size of Data
even and it is useful sometimes, for alignment purposes.
The 2nd union is optional and exists for the non-char cases, so the minimum size of Data
is 2 bytes.
Here we use id
as 1
for char
, 2
for int
and 3
for a vector of strings.
When holding a single
char
it follows theid
so we use just 2 bytesWhen holding an
int
it follows these 2 bytesWhen using strings the code has 2 options:
- for up to 255 strings the count is put at the
size
component, giving an alternate meaning for the 2nd byte ofData
. - When
size
is0
the vector is assumed to be null-terminated and this is useful when there can be a huge number of strings that would overflow size
- for up to 255 strings the count is put at the
output
char: '?'
int: Value is 42
array of strings: [4 in total]
#1 an
#2 array
#3 of
#4 strings
array of strings [null-terminated]:
#1 an
#2 array
#3 of
#4 strings
#5 ends here
5 strings were found...
the code
#include <iso646.h>
#include <stdio.h>
typedef struct
{
unsigned char id; // 1,2,3
union
{
unsigned char the_char;
unsigned char size; // up to 255 strings
};
union
{
int the_int;
const char** argv;
};
} Data;
int check_one(Data*);
int valid_id(Data*);
int main(void)
{
const char* words[] = {"an", "array", "of",
"strings", "ends here", NULL};
Data data[4];
data[0].id = 1;
data[0].the_char = '?';
data[1].id = 2;
data[1].the_int = 42;
data[2].id = 3;
data[2].size = 4;
data[2].argv = words;
data[3].id = 3;
data[3].size = 0; // signals the array as null-terminated
data[3].argv = words;
for (int i = 0; i < 4; i = 1) check_one(&data[i]);
return 0;
}
int check_one(Data* d)
{
const char* what[] = {NULL, "char", "int", "array of strings"};
if (d == NULL) return -1;
if (not valid_id(d)) return -2;
switch (d->id)
{
case 1:
printf("\n%s: '%c'\n", what[d->id], d->the_char);
break;
case 2:
printf("\n%s: Value is %d\n", what[d->id], d->the_int);
break;
case 3:
if (d->size != 0)
{ // known size
printf("\n%s: [%d in total]\n\n", what[d->id], d->size);
for (int i = 0; i < d->size; i = 1)
printf("\t#%d\t%s\n", 1 i, d->argv[i]);
printf("\n");
return 0;
};
// ok: the array is NULL-terminated
printf("\n%s [null-terminated]:\n\n", what[d->id]);
int sz = 0;
for (; d->argv[sz] != NULL; sz = 1)
printf("\t#%d\t%s\n", 1 sz, d->argv[sz]);
printf("\n%d strings were found...\n\n", sz);
break;
default:
break;
}
return 0;
}
int valid_id(Data* d) { return ((d->id > 0) && (d->id < 4)); };