成员在结构中的顺序重要吗?


77

我在C中发现了一种特殊的行为。请考虑以下代码:

 struct s {
     int a;
 };      

 struct z {
     int a;
     struct s b[];
 };  

 int main(void) {
     return 0;
 }   

它编译就好了。然后z像这样更改struct成员的顺序

struct z {
    struct s b[];
    int a; 
};  

突然之间我们得到了编译错误field has incomplete type 'struct s []'

这是为什么?

Answers:


90

字段的顺序struct确实很重要-编译器不允许对字段进行重新排序,因此struct可能会由于添加一些填充而发生变化。

但是,在这种情况下,您要定义一个所谓的flexible成员,该成员的大小可以更改。灵活成员的规则是

  • 这样的成员可能永远不会超过一个,
  • 如果存在,则柔性构件必须是中的最后一个struct,并且
  • struct必须除了灵活的至少一个成员。

请查看此问答,以获取有关使用柔性结构构件的简短说明。


5
看起来,诊断似乎非常差(尽管在技术上是准确的)。
Lightness Races in Orbit

5
@LightnessRacesinOrbit是的,我也不特别喜欢错误消息。
谢尔盖·卡里尼琴科2014年

21

编译器无法计算多少内存 struct s b[];将消耗。这意味着,如果该结构后面有任何字段,则编译器将无法确定这些字段的位置。

在C的旧版本中,过去(例如)struct s b[];不允许作为结构的成员。这使高效的内存管理变得烦人。举一个简单的例子,假设您有一个包含“名称”字符串的结构(可以是几个字符,也可以是很多字符)。您可以使用足够大的固定大小数组来存储最大名称(这会浪费空间),也可以使用指针分配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
有趣的是,在灵活数组成员成为标准组件之前,我记得使用过一些编译器(无论是设计还是偶然),这些编译器将使代码声明大小为零的数组作为结构的一部分,这可能是因为省略了零数组大小检查将导致默认情况下允许此类数组。使用empty[]在语义上略有不同,因为编译器将允许直接实例化包含零大小数组的结构(尽管使用该数组将是UB),而FLA或包含一个size的结构必须是其中的最后一个元素一个结构。
2014年

@supercat:是的,零长度数组至少在版本2(gcc.gnu.org/onlinedocs/gcc-2.95.3/gcc_4.html#SEC74)中作为GCC的扩展引入了,即使现在它们仍然受支持(gcc.gnu.org/onlinedocs/gcc/Zero-Length.html)。
Nemo 2014年

15

成员的顺序通常很重要(即,字段之间可能会插入一些填充),但是在您的特定情况下,您使用的是灵活的成员数组,这在C99-6.7.2.1.16中已标准化

作为一种特殊情况,具有多个命名成员的结构的最后一个元素可能具有不完整的数组类型;这称为灵活数组成员。在大多数情况下,柔性数组成员将被忽略。特别地,该结构的尺寸就好像省略了柔性阵列构件,除了其可能具有比省略所暗示的更多的尾随填充。

您的struct s b[];成员将用于访问多个struct s元素的动态堆分配。


1
感谢您引用标准。我经常想知道这种结构的大小是多少。
2014年

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


1

在这种情况下,订单确实很重要。您struct z包含一个由组成的数组structs s。但是,此数组没有与之关联的大小,因此编译器不知道如何分配适当的堆栈空间,因为此后还有struct(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)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.