Home > OS >  using simple void pointer for arrays of C strings
using simple void pointer for arrays of C strings

Time:12-17

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 the id so we use just 2 bytes

  • When holding an int it follows these 2 bytes

  • When 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 of Data.
    • When size is 0 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

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)); };
  •  Tags:  
  • c
  • Related