【问题标题】:Why has the type of an empty array a different size within and outside of a structure?为什么空数组的类型在结构内部和外部具有不同的大小?
【发布时间】:2020-12-28 00:08:35
【问题描述】:
#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
}

如上图,为什么struct内的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

正如 cmets 所指出的,这可能仅适用于 C 而不是 C++。所以我把代码改成了C版本。

【问题讨论】:

  • Struct padding 可能是因为对齐?
  • 这是未定义的行为。
  • 绝对是UB。我不知道为什么在面试中被问到会改变任何事情。在采访中似乎是一个合理的答案。如果面试官说某些特定的输出是有保证的,那他们就错了。
  • @BAKEZQ 面试官确实错了。数组大小必须大于 0:timsong-cpp.github.io/cppwp/dcl.array#1.sentence-3
  • OP:你试过用-pedantic编译这个吗? “警告:ISO C++ 禁止零大小数组”.

标签: arrays c structure padding memory-alignment


【解决方案1】:

首先,您的代码是未定义的行为。来自Arrays p1强调我的:

在声明 T D 中,其中 D 具有以下形式

  D1 [ constant-expressionopt ] attribute-specifier-seqopt 

以及包含的类型 声明 T D1 中的 declarator-id 是“derived-declarator-type-list T”,D中的declarator-id的类型是 “N T 的派生声明符类型列表数组”。常量表达式 应为 std​::​size_t 类型的转换常量表达式 ([expr.const])。 其值 N 指定数组边界,即 数组中的元素数量; N 应大于零。

数组的大小必须大于 0。


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

虽然零长度数组的大小为零,但由于尾部填充,这种数组成员可能会增加封闭类型的大小。

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

【讨论】:

  • 你能解释更多关于尾部填充或简单地添加一个链接。
【解决方案2】:

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

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

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

uint8_t c 成员的大小为 1,对齐要求为 1。到目前为止,该结构需要 9 个字节。

对于uint64_t d[0],C 标准未定义行为。然而,除非面试官明确指出这是一个关于严格遵守标准 C 的问题,否则回答行为未定义是不够的,因为 C 不仅仅是标准。也有符合(但不严格符合)C 和非标准 C 变体。GCC 支持一个众所周知的扩展,其中结构的最后一个成员可以声明为具有零元素的数组,面试官期待提问者请注意这一点。

当使用这种结构时,程序必须为它希望使用的任何数组元素分配足够的空间,方法是将这些空间添加到使用malloc 或类似的内存分配例程发出的请求中。例如,要为基础结构加上 13 个元素分配空间,可以使用malloc(sizeof(struct Obj) + 13 * sizeof(uint64_t))

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-11-22
    • 1970-01-01
    • 2011-06-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多