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;
}
灵活数组的一般用法是使用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;
}