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.
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));
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;
}