C 灵活的数组用另一种类型而不是 malloc 定义

C flexible array define with another type instead of malloc

灵活数组的一般用法是使用malloc来定义灵活数组。我正在尝试探索用另一个结构定义灵活的数组。一个例子

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;

据我了解,需要在结构的末尾定义灵活的数组。并且 clangd 会给出以下警告。 -Wgnu-variable-sized-type-not-at-end

我只是想问一下以前有没有人这样做过,安全吗?或者有没有更好的方法来定义不分配的灵活数组大小?

然后您可以将对象的用法包装在宏中以静态断言 ext.base.data == ext.data,然后再转换并传递给一般 API 消耗 test_base_t。这样你就可以拥有编译而不是分配所需的内存。

编辑

我想怎么消费它似乎有点混乱,这里有一个例子来演示

#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));

C 2018 6.7.2.1 3 says of a structure containing a flexible array member:

… such a structure (and any union containing, possibly recursively, a member that is such a structure) shall not be a member of a structure or an element of an array.

因此,问题中的test_t类型违反了这个“应该”的要求,C 2018 4 2说这使得行为没有被C标准定义。编译器可能会拒绝此代码。如果编译器接受它,则程序的行为不是由 C 标准定义的。

作为可能出错的示例(因为 C 标准允许),请考虑以下代码:

test_t test = { .base = { .data_size = 3, }, .data = { 1, 2, 3, }, };
printf("%g\n", test.base.data[0]);

由于从未通过该表达式为 test.base.data[0] 赋值,并且标准未将 test.data 定义为别名 test.base.data,因此编译器可能会假定 test.base.data[0] 未初始化,因此未指定,并且此 printf 可以使用 float 类型的任何值,即使 test.base.data[0]test.data[0] 名义上引用相同的内存。

在这段代码中:

test_t test = { .base = { .data_size = 3, } };
for (int i = 0; i < 4; ++i)
    test.base.data[i] = i+1;
test_t copy = test;

编译器可能会假设,由于 test.data 从未被初始化,因此在从 test.

初始化它时不需要将其复制到 copy

您不能使用初始化数组创建 test_base_t 的实际实例,但您可以使用指定长度的初始化数组创建复合文字,并将其地址转换为 test_base_t 指针。两个结构的布局和对齐应该兼容,因为它们具有完全相同的类型,除了灵活的数组长度。

这是一个例子:

#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;
}

这是另一种方法,可能更接近您的期望,使用 union 和具有灵活类型的 base 成员以及具有适当数组大小的参数实例类型:

#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;
}