Search code examples
cpointersc99structdereference

Accessing arrays in a pointer to a struct


I have a simple struct:

typedef struct {
    void *things;
    int sizeOfThings;
} Demo;

things is intended to contain an array of individual "thing", like maybe strings or ints. I create a pointer to it:

Demo * Create(int value) {
    Demo *d = malloc(sizeof(Demo));
    if (d != NULL) {
        d->sizeOfThings = value;
        d->things = malloc(20 * value); // We'll have a max of 20 things
    }
}

value is sizeof(int) for an array of ints, for example.

If in another function I want to insert something into d->things (assuming at least for not that I'm just adding it to the first slot, position management done elsewhere):

char * thing = "Me!";
strncpy(d->things[0], &thing, d->sizeOfThings);

I get around the strncpy area

test.c:10: warning: pointer of type ‘void *’ used in arithmetic
test.c:10: warning: dereferencing ‘void *’ pointer
test.c:10: error: invalid use of void expression

I'm just trying to understand the use of void* as a way to generalize my functions. I suspect there's something wrong with d->things[0].


Solution

  • According to the C standard, void has no size-- sizeof(void) is undefined. (Some implementations make it sizeof(int) but this is non-compliant.)

    When you have an array of type foo, this expression:

    array[3]
    

    Adds 3*sizeof(foo) to the address stored in array and then deferences that. That's because the values are all packed together in memory. Since sizeof(void) is undefined, you can't do that for void arrays (in fact you can't even have void arrays, only void pointers.)

    You must cast any void pointer to another pointer type before treating it as an array:

    d->things = malloc(20 * sizeof(int));
    (int *)(d->things)[0] = 12;
    

    However, keep in mind that you don't even have to do that to use strncpy on it. Strncpy can accept a void pointer just fine. But you were using strncpy incorrectly. Your strncpy invocation should look like this:

    strncpy(d->things, thing, d->sizeOfThings);
    

    What your version would have done was try to treat the first array member of d->things as a pointer when it's not, and would have treated &thing, which is a char **, as if it were just a char *.