struct结尾处的大小为0的数组

时间:2016-04-12 15:01:57

标签: c arrays pointers gcc struct

我的系统编程课程教授今天告诉我们,最后定义一个零长度数组的结构:

struct array{
    size_t size;
    int data[0];
};

typedef struct array array;

这是一个有用的结构,用于定义或初始化带有变量的数组,即如下所示:

array *array_new(size_t size){
    array* a = malloc(sizeof(array) + size * sizeof(int));

    if(a){
        a->size = size;
    }

    return a;
}

也就是说,使用malloc(),我们还为大小为零的数组分配内存。这对我来说是全新的,而且看起来很奇怪,因为根据我的理解,结构在连续的位置上没有必要的元素。

为什么array_new中的代码会将内存分配给data[0]?那么为什么访问是合法的,比如说

array * a = array_new(3);
a->data[1] = 12;

从他告诉我们的情况来看,似乎在结构的末尾定义为长度为零的数组确保紧跟在结构的最后一个元素之后,但这看起来很奇怪,因为,再次,根据我的理解,结构可能有填充。

我也看到这只是gcc的一个功能,并没有被任何标准定义。这是真的吗?

5 个答案:

答案 0 :(得分:37)

目前,存在标准功能,如C11第6.7.2.1章中所述,称为灵活数组成员

引用标准,

  

作为一种特殊情况,具有多个命名成员的结构的最后一个元素可以   有一个不完整的数组类型;这被称为灵活的阵列成员。在大多数情况下,   灵活的数组成员被忽略。特别是,结构的大小就像是   省略了灵活的数组成员,除了它可能有更多的尾随填充   遗漏意味着。 [...]

语法应为

struct s { int n; double d[]; };

最后一个元素是不完整类型,( 没有数组维度,甚至不是0 )。

因此,您的代码应该更像

struct array{
    size_t size;
    int data[ ];
};

符合标准。

现在,举一个0大小的数组,这是一种实现相同目标的传统方式(“struct hack”)。在C99之前,GCC supported this as an extension模拟灵活的数组成员功能。

答案 1 :(得分:23)

你的教授很困惑。他们应该阅读what happens if I define a zero size array。这是一个非标准的GCC扩展;它不是有效的C而不是他们应该教给学生使用的东西(*)。

相反,请使用标准C 灵活数组成员。与零大小的阵列不同,它实际上可以工作,便携式:

struct array{
    size_t size;
    int data[];
};

当您在结构上使用sizeof时,保证灵活数组成员计为零,允许您执行以下操作:

malloc(sizeof(array) + sizeof(int[size]));

(*)早在90年代,人们使用不安全的漏洞来在结构之后添加数据,称为“struct hack”。为了提供扩展结构的安全方法,GCC将零大小的数组功能实现为非标准扩展。它在1999年变得过时,当时C标准最终提供了一种更好的方法来实现这一目标。

答案 2 :(得分:7)

其他答案解释了零长度数组是GCC扩展,C允许可变长度数组,但没有人解决您的其他问题。

  根据我的理解,结构在连续的位置上没有必要的元素。

是。 struct数据类型的元素不一定在连续的位置。

  

为什么array_new中的代码会将内存分配给data[0]?那么为什么访问是合法的,比如说

array * a = array_new(3);
a->data[1] = 12;
     

您应该注意,零长度数组的一个限制是它必须是结构的最后一个成员。通过这种方式,编译器知道结构可以具有可变长度的对象,并且在运行时将需要更多的内存 但是,你不应该混淆; “因为零长度数组是结构的最后一个成员,那么为零长度数组分配的内存必须添加到结构的末尾,并且因为结构不必将它们的元素连续放在连续的位置,那么怎么可能呢?分配内存是否可以访问?

没有。事实并非如此。结构成员的内存分配不一定是连续的,它们之间可能有填充,但必须使用变量data访问分配的内存。是的,填充对此没有任何影响。规则是: §6.7.2.1/ 15

  

在结构对象内,非位字段成员和位域中的单位   驻留的地址会按声明的顺序增加。

  

我也看到这只是gcc的一个功能,并没有被任何标准定义。这是真的?

是。正如其他答案已经提到的那样,标准C不支持零长度数组,而是GCC编译器的扩展。 C99引入了灵活数组成员。 C标准(6.7.2.1)中的一个例子:

  

声明后:

struct s { int n; double d[]; };
     

结构struct s具有灵活的数组成员d。使用它的典型方法是:

int m = /* some value */;
struct s *p = malloc(sizeof (struct s) + sizeof (double [m]));
     

并假设对malloc的调用成功,p指向的对象在大多数情况下都表现为,如同p已被声明为:

struct { int n; double d[m]; } *p;
     

(在某些情况下,这种等价性被破坏;特别是,成员d的偏移量可能不同。)

答案 3 :(得分:1)

更标准的方法是定义数据大小为1的数组,如:

struct array{
    size_t size;
    int data[1]; // <--- will work across compilers
};

然后在计算中使用数据成员的偏移量(不是数组的大小):

array *array_new(size_t size){
    array* a = malloc(offsetof(array, data) + size * sizeof(int));

    if(a){
        a->size = size;
    }

    return a;
}

这实际上是使用array.data作为额外数据可能去的位置的标记(取决于大小)。

答案 4 :(得分:1)

我以前的方式是在结构的末尾没有虚拟成员:结构本身的大小告诉你刚刚过去的地址。将1添加到类型指针:

header * p = malloc (sizeof (header) + buffersize);
char * buffer = (char*)(p+1);

对于一般的结构,你可以知道这些字段是按顺序排列的。能够匹配文件格式二进制映像,操作系统调用或硬件所需的某些强制结构是使用C的一个优点。您必须知道对齐的填充是如何工作的,但它们是按顺序并且在一个连续的块中。