简体   繁体   English

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

[英]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.灵活数组的一般用法是使用malloc来定义灵活数组。 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.并且 clangd 会给出以下警告。 -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 .然后,您可以将 object 的用法包装在一个宏中,以 static 断言ext.base.data == ext.data ,然后再转换并传递给一般的 API 消耗test_base_t This way you can have the memory required in compile instead of allocing.这样你就可以得到编译而不是分配所需的 memory。

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

C 2018 6.7.2.1 3 says of a structure containing a flexible array member: C 2018 6.7.2.1 3 说了一个包含灵活数组成员的结构:

… 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. …这样的结构(以及任何联合,可能递归地包含这样一个结构的成员)不应是结构的成员或数组的元素。

Thus, the test_t type in the question violates this “shall” requirement, and C 2018 4 2 says that makes the behavior not defined by the C standard.因此,问题中的test_t类型违反了此“应”要求,并且 C 2018 4 2 表示该行为未被 C 标准定义。 A compiler could reject this code.编译器可能会拒绝此代码。 If the compiler accepts it, the behavior of the program is not defined by the C standard.如果编译器接受它,则程序的行为不是由 C 标准定义的。

As an example of what could go wrong (in that the C standard allows it), consider this code:作为 go 可能出错的示例(因为 C 标准允许它),请考虑以下代码:

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

Since test.base.data[0] was never assigned a value through that expression, and the standard does not define test.data to alias test.base.data , the compiler may assume the value of test.base.data[0] is uninitialized and hence unspecified, and this printf may use any value of the float type, even if test.base.data[0] and test.data[0] nominally refer to the same memory.由于test.base.data[0]从未通过该表达式赋值,并且标准未将test.data定义为别名test.base.data ,因此编译器可能会假定test.base.data[0]的值未初始化,因此未指定,并且此printf可以使用float类型的任何值,即使test.base.data[0]test.data[0]名义上引用相同的 memory。

And in this code:在这段代码中:

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

The compiler may assume that, since test.data was never initialized, it does not need to be copied to copy when initializing it from test .编译器可能会假设,因为test.data从未初始化过,所以在从test初始化它时不需要copy它。

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.您不能使用初始化数组创建test_base_t的实际实例,但您可以使用指定长度的初始化数组创建复合文字,并将它们的地址转换为test_base_t指针。 The layout and alignment of both structures should be compatible, given that they have exactly the same types, save for the flexible array length.两个结构的布局和 alignment 应该是兼容的,因为它们具有完全相同的类型,除了灵活的数组长度。

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

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM