arrayscstruct

How can we define nullable arrays in C structs which are allocated outside?


For optimization reasons, I'd like arrays in struct to be "nullable", so that I can allocate memory for it later. Like array parameters in functions.

This means, I need an array inside a struct which should compile to a pointer and which will be assigned a dynamically allocated array later, but otherwise behaves like a "normal array" inside a struct.

Example:

struct MyStruct
{
    typeof(int (*)[4]) elements;
};
struct MyStruct myStruct;

int array[4];
myStruct.elements = array;  // option 1

typeof(int[4]) *array2 = malloc(sizeof(myStruct.elements));
myStruct.elements = array2;  // option 2
myStruct.elements[0] = 0xC;
myStruct.elements[1] = 0x0;
myStruct.elements[2] = 0xD;
myStruct.elements[3] = 0xE;

printf("%x %x %x %x\n", array2[0], array2[1], array2[2], array2[3]);
…

However, this doesn't work. It creates a pointer to an array so that myStruct.elements[0] is the first array, not the first element of an array. And sizeof(myStruct.elements) will be the size of a pointer.

Of course, we can try

struct MyStruct
{
    int *elements;
};

but int * is lacking type information compared to int [4] which leads to weaker type checking. It would allow for

int bad_array[3];

myStruct.elements = bad_array;

sizeof will be missing the information about the number of elements. IDEs also won't show the number of elements in IDE hints.

We could add an elements_count member field, but doing so, doesn't allow for compile-time type checking. I only need the element count to be constant. Besides, an extra member field also uses additional memory.


Solution

  • As far as I know, the C programming language doesn't allow arrays which compile to a pointer and whose sizeof() is the size of the array.

    We can use a union to come sufficiently close to the desired outcome.

    struct MyStruct
    {
        union {
            typeof(int [4]) *elements;
            int *const element;
        };
    }
    

    The constant element pointer accesses single elements and also prevents pointer assignments which would have weaker type checking.

    struct MyStruct myStruct;
    
    int array[4] = { 0xC, 0x0, 0xD, 0xE };
    myStruct.elements = &array;  // okay
    
    // int bad_array[3];
    // myStruct.elements = &bad_array;  // type error
    
    printf("%x %x %x %x\n", myStruct.element[0], myStruct.element[1], myStruct.element[2], myStruct.element[3]);
    

    The only drawback is, we need to dereference the pointer for sizeof

    myStruct.elements = malloc(sizeof(*myStruct.elements));
    

    I think, it does make sense, considering that malloc allocates the memory of *myStruct.elements and not the memory of the pointer myStruct.elements. This is a common pointer allocation pattern.