为什么空数组的类型在结构内部和外部具有不同的大小?

Why has the type of an empty array a different size within and outside of a structure?

#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之后立即堆叠,更奇怪的是空数组在结构之外的大小为零。

其实是一道面试题。解释是这样的,还是看不懂

If there is no fourth field, it should be 4+4+4=12, plus the fourth field is 16, the fourth field does not occupy space, but it will tell the compiler to align by 8 bytes

This usage is often used in the kernel, for example, the following can be directly accessed by subscript

Obj o1; uint64_t array[1024]; // In memory, array immediately follows
o1 o1.d[123]; // can access the elements of array

如评论所述,这可能仅适用于 C 而不是 C++。所以我将代码更改为C版本。

首先,你的代码是未定义的行为。来自 个数组 p1 强调我的:

In a declaration T D where D has the form

  D1 [ constant-expressionopt ] attribute-specifier-seqopt 

and the type of the contained declarator-id in the declaration T D1 is “derived-declarator-type-list T”, the type of the declarator-id in D is “derived-declarator-type-list array of N T”. The constant-expression shall be a converted constant expression of type std​::​size_­t ([expr.const]). Its value N specifies the array bound, i.e., the number of elements in the array; N shall be greater than zero.

数组的大小必须大于 0。


至于 gcc 编译器扩展 在 C 代码中 allows for zero sized arrays 并且碰巧在 C++ 代码中也受支持,gcc 文档指出:

Although the size of a zero-length array is zero, an array member of this kind may increase the size of the enclosing type as a result of tail padding.

这似乎发生在您的代码中。

这个面试问题考察了候选人在 C 标准和特定实现中的对齐和某些语义的知识。

char a 成员的大小为一(字节),对齐要求为一(字节)。

uint32_t b 成员的大小为四,通常有四个字节的对齐要求。为了将其放置在四字节倍数的地址上,编译器必须在 a 之后和 b 之前包括三个未使用的字节,它们称为填充字节。至此,该结构需要1+3+4 = 8个字节。

uint8_t c 成员的大小为一,对齐要求为一。至此,该结构需要9个字节。

对于 uint64_t d[0],该行为未由 C 标准定义。但是,除非面试官指定这是一个关于严格遵守标准 C 的问题,否则回答行为未定义是不充分的,因为 C 不仅仅是标准。还有符合(但不严格符合)C 和 C 的 non-standard 变体。GCC 支持 well-known 扩展,其中结构的最后一个成员可以声明为具有零元素的数组,并且面试官希望提问者意识到这一点。

当使用这样的结构时,程序必须为它希望使用的任何数组元素分配足够的 space,方法是将这样的 space 添加到 malloc 或类似的 memory-allocation 例程。例如,要为基本结构加上 13 个元素分配 space,可以使用 malloc(sizeof(struct Obj) + 13 * sizeof(uint64_t)).

通常,uint64_t 有八个字节的对齐要求。无论其对齐要求是什么,编译器都会在成员 cd 之间添加足够的未使用字节,以确保 d 具有正确的对齐方式。如果确实需要eight-byte对齐,那么在c之后必须插入7个字节,所以到d开头的结构大小为1+3+4+1+7 = 16 个字节。