我在C中发现了一种奇特的行为。请考虑以下代码:
struct s {
int a;
};
struct z {
int a;
struct s b[];
};
int main(void) {
return 0;
}
它编译得很好。然后改变结构z
成员的顺序,如此
struct z {
struct s b[];
int a;
};
突然间我们得到了编译错误field has incomplete type 'struct s []'
。
为什么?
答案 0 :(得分:88)
struct
中字段的顺序很重要 - 不允许编译器对字段重新排序,因此struct
的大小可能会因添加一些填充而改变。
但是,在这种情况下,您要定义一个所谓的灵活成员,这是一个可以更改大小的数组。灵活成员的规则是
struct
和struct
除了灵活的成员外,必须至少有一个成员。请参阅this Q&A,了解使用灵活结构成员的小例子。
答案 1 :(得分:21)
编译器无法计算struct s b[];
将消耗多少内存。这意味着如果结构后面有任何字段,编译器就无法确定这些字段的位置。
过去(在旧版本的C中)(例如)struct s b[];
不允许作为结构的成员。这使得高效的内存管理变得烦人。举一个简单的例子,假设你有一个包含“name”字符串的结构(可能只是几个字符或很多字符)。您可以使用固定大小的数组,该数组足以容纳最大的名称(浪费空间),或者使用指针并分配2个内存(一个用于结构,一个用于可变长度名称字符串)。或者,您可以使用指针并使其指向结构末端之外的额外空间,最终会出现如下情况:
length = strlen(my_string);
foo = malloc(sizeof(MYSTRUCTURE) + length + 1);
foo->name = (void *)foo + sizeof(MYSTRUCTURE); // Set pointer to extra bytes past end of structure
memcpy(foo->name, my_string, length + 1);
这是最有效的选择;但它也很丑陋且容易出错。
为了解决这个问题,编译器添加了非标准扩展,以允许在结构的末尾使用“未知大小的数组”。这使程序员更容易一些,并使其更高效(因为不需要额外的指针成员)。这最终被C标准采用(也许在C99 - 我不记得了)。
答案 2 :(得分:15)
成员的顺序通常很重要(例如,某些填充可能会插入字段之间),但在您的特定情况下,您使用灵活成员数组,这在C99 - 6.7中已标准化。 2.1.16
作为一种特殊情况,具有多个命名成员的结构的最后一个元素可能具有不完整的数组类型;这被称为灵活的阵列成员。在大多数情况下,将忽略灵活数组成员。特别是,结构的大小就好像省略了柔性阵列构件一样,除了它可能有比尾部填充更多的尾随填充。
您的struct s b[];
成员用于访问多个struct s
元素的动态堆分配。
答案 3 :(得分:4)
您问题的标题是“struct
成员的订单是否有问题?”。
您的代码中的明显问题与您的struct
包含灵活成员的事实有关。
所以这是一个与struct
中成员顺序的一般问题相关的其他问题:
以以下两种结构为例:
struct s1
{
int a;
short b;
char c;
};
struct s2
{
char c;
short b;
int a;
};
大多数编译器都会添加填充,以便将每个成员对齐到可被其大小整除的地址。
所以struct s2
可能最终编译成:
struct s2
{
char c;
char pad1;
short b;
short pad2;
short pad3;
int a;
};
这最终会导致struct s1
和struct s2
类型实例的大小不同。
答案 4 :(得分:1)
在这种情况下,订单确实很重要。您的struct z
包含一个由structs s
组成的数组。但是,此数组没有与之关联的大小,因此编译器不知道如何分配适当的堆栈空间,因为之后还有另一个结构域(int a
)。它将如何运作的一个例子:
struct s {
int a;
}
struct z {
struct s b[10];
int a;
}
int main(void) {
return 0;
}
如果你真的需要数组来改变大小,最好是在堆上分配整个结构,并将数组作为指向struct s
的指针,然后动态地重新分配它以适应不断变化的数组大小。查看malloc (3)
,realloc 3)
,calloc (3)
和free (3)
。