问题描述
#include <stdio.h>
struct Obj {
char a;
uint32_t b;
uint8_t c;
uint64_t d[0];
};
struct Obj1 {
uint64_t d[0];
};
int main() {
uint64_t a[0];
printf("%d\n",sizeof(Obj)); // 16
printf("%d\n",sizeof(a)); // 16
printf("%d\n",sizeof(Obj1)); // 16
//cout << sizeof(Obj) << endl; // 16
//cout << sizeof(a) << endl; // 0
//cout << sizeof(Obj1) << endl; // 0
}
如上所示,为什么结构中的uint64_t
变量不会在uint8_t
之后立即返回,而且更奇怪的是空数组在结构外部的大小为零。
这实际上是一个面试问题。解释是这样的,尽管仍然无法理解。
如果没有第四个字段,则应为4 + 4 + 4 = 12,加上第四个 字段是16,第四个字段不占用空间,但是它将告诉 编译器对齐8个字节
此用法通常在内核中使用,例如,以下可以 被下标直接访问
Obj o1; uint64_t array[1024]; // In memory,array immediately follows
o1 o1.d[123]; // can access the elements of array
正如评论所指出的,这可能仅适用于C而不是C ++。所以我将代码更改为C版本。
解决方法
首先,您的代码是未定义的行为。来自Arrays p1的重点:
在声明T D中,D的形式为
D1 [ constant-expressionopt ] attribute-specifier-seqopt
以及所包含的类型 声明T D1中的declarator-id是“ derived-declarator-type-list” T”,D中声明符的类型为 “ NT的派生声明符类型列表数组”。常量表达式 应该是std :: size_t类型的转换常量表达式 ([expr.const])。 其值N指定数组边界,即 数组中元素的数量; N应大于零。
数组的大小必须大于0。
对于 gcc编译器扩展,allows for zero sized arrays在C代码中并且恰好在C ++代码中也受支持,gcc文档指出:
尽管零长度数组的大小为零,但由于尾部填充,这种数组成员可能会增加封闭类型的大小。
您的代码中似乎正在发生这种情况。
,这个面试问题探究了候选人在C标准和特定实现中的对齐方式和某些语义。
char a
成员的大小为1(字节),对齐要求为1(字节)。
uint32_t b
成员的大小为4,通常具有四个字节的对齐要求。为了将其放置在四个字节的倍数的地址上,编译器必须在a
之后和b
之前包括三个未使用的字节,它们正在调用填充字节。到目前为止,该结构需要1 + 3 + 4 = 8个字节。
uint8_t c
成员的大小为1,对齐要求为1。到目前为止,该结构需要9个字节。
对于uint64_t d[0]
,行为不是由C标准定义的。但是,除非访问员指定了这是关于严格符合标准C的问题,否则回答行为未定义是不充分的,因为C不仅限于标准。还存在C(但不是严格符合)的C和非标准的C变体。GCC支持众所周知的扩展,其中结构的最后一个成员可以声明为具有零元素的数组,并且访调员希望提问者要知道这一点。
使用这种结构时,程序必须通过将这样的空间添加到使用malloc
或类似的内存分配例程的请求中,为它希望使用的任何数组元素分配足够的空间。例如,要为基本结构和13个元素分配空间,可以使用malloc(sizeof(struct Obj) + 13 * sizeof(uint64_t))
。
通常,uint64_t
的对齐要求为八个字节。无论其对齐要求是什么,编译器都会在成员c
和d
之间添加足够的未使用字节,以确保d
具有正确的对齐方式。如果确实需要八字节对齐,则必须在c
之后插入七个字节,因此直到d
开头的结构大小为1 + 3 + 4 + 1 + 7 = 16字节