cflexible-array-member

C flexible array define with another type instead of malloc


the general usage of flexible array is to use malloc to define the flexible array. I'm trying to explore defining the flexible array with another struct. An example

typedef struct {
    uint64_t header;
    size_t data_size;
    float data[];
} test_base_t;

typedef struct {
    test_base_t base;
    float data[3];
} test_t;

As I understand, flexible array needs to be defined at the end of a struct. And clangd will give the following warning. -Wgnu-variable-sized-type-not-at-end

I just wanted to ask if anybody has done this before and is it safe? Or is there a better way to define flexible array size without alloc?

You can then wrap the usage of the object in a macro to static assert ext.base.data == ext.data before casting and passing to a general API consumes test_base_t. This way you can have the memory required in compile instead of allocing.

Edit

There seem to be a confusion on how I wanted to consume it, here is an example to demonstrate

#define SUM_BASE(test) \
    sum_base(&test->base); \
    _Static_assert(test->data == test->base.data);

float sum_base(test_base_t *base)
{
  float sum = 0;
  for (size_t i = 0; i < base->data_size; i++)
  {
    sum += base->data[i];
  }
  return sum;
}

test_t test = { .base = { .data_size = 3, }, .data = { 1, 2, 3, }, };
SUM_BASE((&test));

Solution

  • You cannot create actual instances of test_base_t with an initialized array, but you can create compound literals with an initialized array of a specified length and cast their address as test_base_t pointers. The layout and alignment of both structures should be compatible, given that they have exactly the same types, save for the flexible array length.

    Here is an example:

    #include <inttypes.h>
    #include <stdio.h>
    #include <stdint.h>
    
    typedef struct {
        uint64_t header;
        size_t data_size;
        float data[];
    } test_base_t;
    
    #define TEST_ARRAY(n) (test_base_t*)&(struct { uint64_t header;  \
                                                   size_t data_size; \
                                                   float data[n]; })
    
    float sum_base(const test_base_t *p) {
        float sum = 0.F;
        for (size_t i = 0; i < p->data_size; i++) {
            sum += p->data[i];
        }
        return sum;
    }
    
    void print_test(const test_base_t *p) {
        printf("%"PRIu64" %zu { ", p->header, p->data_size);
        if (p->data_size) {
            printf("%g", p->data[0]);
            for (size_t i = 1; i < p->data_size; i++) {
                printf(" %g", p->data[i]);
            }
        }
        printf(" } sum=%g\n", sum_base(p));
    }
    
    int main() {
        test_base_t *p1 = TEST_ARRAY(1){.data_size = 1, .data = {1}};
        test_base_t *p2 = TEST_ARRAY(2){.data_size = 2, .data = {1, 2}};
        print_test(p1);
        print_test(p2);
        print_test(TEST_ARRAY(3){.data_size = 3, .data = {1, 2, 3}});
        print_test(TEST_ARRAY(4){.data_size = 4, .data = {1, 3, 5, 7}});
        return 0;
    }
    

    Here is another approach, perhaps closer to your expectations, using a union with a base member with the flexible type and a parametric instance type with the appropriate array size:

    #include <inttypes.h>
    #include <stdio.h>
    #include <stdint.h>
    
    typedef struct {
        uint64_t header;
        size_t data_size;
        float data[];
    } test_base_t;
    
    /* parametric type template using C macros */
    /* structures with a flexible array can be members of union types */
    #define test_base_t(...) \
        union { \
            test_base_t base; \
            struct { \
                uint64_t header; \
                size_t data_size; \
                float data[__VA_ARGS__]; \
            }; \
        }
    
    float sum_base(const test_base_t *p) {
        float sum = 0.F;
        for (size_t i = 0; i < p->data_size; i++) {
            sum += p->data[i];
        }
        return sum;
    }
    
    void print_test(const test_base_t *p) {
        printf("%"PRIu64" %zu { ", p->header, p->data_size);
        if (p->data_size) {
            printf("%g", p->data[0]);
            for (size_t i = 1; i < p->data_size; i++) {
                printf(" %g", p->data[i]);
            }
        }
        printf(" } sum=%g\n", sum_base(p));
    }
    
    int main() {
        test_base_t(1) t1 = { .data_size = 1, .data = {1} };
        test_base_t(2) t2 = { .data_size = 2, .data = {1, 2} };
        /* the print_test function can be called without casts */
        print_test(&t1.base);
        print_test(&t2.base);
        print_test(&((test_base_t(3)){.data_size = 3, .data = {1, 2, 3}}).base);
        print_test(&((test_base_t(4)){.data_size = 4, .data = {1, 3, 5, 7}}).base);
        return 0;
    }