字符数组大小未被计算。

7

我有以下代码:

#include <stdio.h>
#include <stdint.h>

typedef struct E_s {
    uint32_t    a;
    uint32_t    b;
    uint32_t    c;
} E_t;

typedef struct S_s {
    uint32_t    data_sz;
    char        data[];
} S_t;

typedef struct F_s {
    E_t     E;
    S_t     S;
    char        data[16];
//} __attribute__((packed)) full_msg_t;
} F_t;


int main(int argc, char* argv[])
{
    F_t out;
    printf("sizeof(out.data) = %lu\n", sizeof(out.data));
    printf("sizeof(out.E) = %lu\n", sizeof(E_t));
    printf("sizeof(out.S) = %lu\n", sizeof(S_t));
    printf("sizeof(out) = %lu\n", sizeof(F_t));

    return EXIT_SUCCESS;
}

当我运行代码时,我看到以下输出:

sizeof(out.data) = 16
sizeof(out.E) = 12
sizeof(out.S) = 4
sizeof(out) = 32
问题:为什么S_t的大小是4(输出的第三行)?我本来期望它是8(uint32_t+char[])。为什么char[]的大小没有计入?
此外,out.dataout.S.data都指向同一内存位置,这导致我深入挖掘并发现了上述观察结果。任何线索都将非常有帮助。我没有预料到这两个变量会重叠。

3
不可以在一个结构体中放置具有可变数组成员的结构体。 - CGi03
2
为什么 char[] 数组的大小没有被包括在内?一个有 0 个元素的数组的大小是多少? - Gerhardh
你期望 sizeof(S_t) 是多少? - Jabberwocky
所有的答案都说灵活数组成员不会影响大小,但值得注意的是这样的成员不能贡献大小。sizeof对于任何给定类型始终返回相同的值,因此它不可能包括数组的大小,因为那不是类型的一部分。 - ikegami
关于 "我期望它是8(uint32_t + char[]",char data[] 不是一个指针。访问 data 允许您访问跟随 struct S_s 的字节。在这种情况下,out.S.data 只是访问 out.data 的另一种方式。 - ikegami
1
严格来说,打印 size_t 值的格式(例如 sizeof 返回的值)是 %zu,其中 z 是长度修饰符,表示 size_t - Jonathan Leffler
4个回答

8
在这个结构体中:
typedef struct S_s {
    uint32_t    data_sz;
    char        data[];
} S_t;
data成员是一个灵活的数组成员。这样的成员不会影响结构体的大小,因为它的大小没有指定。这在C标准的第6.7.2.1p18节中有明确规定:

作为一个特例,具有多个命名成员的结构的最后一个元素可以具有不完整的数组类型;这称为灵活数组成员。在大多数情况下,灵活数组成员将被忽略。特别地,结构的大小就像灵活数组成员被省略一样,除了它可能有比省略所暗示的更多的尾随填充。

因此,S_t的大小不包括data成员,这就是为什么sizeof(S_t)为4的原因。
只有在为结构体动态分配内存时才能使用这样的成员。例如:
S_t *s = malloc(sizeof(S_t) + 10);

这使您可以访问s->data[0]s->data[9]
这也意味着您不能将具有灵活数组成员的结构体放入另一个结构体或数组中,因为无法准确知道灵活数组成员的结束位置。
这在第6.7.2.1p3节中详细说明:

结构体或联合体不得包含具有不完整或函数类型的成员(因此,结构体不得包含自身的实例,但可以包含指向其自身实例的指针),但是,具有多个命名成员的结构体的最后一个成员可以具有不完整的数组类型;这样的结构体(以及可能递归包含此类结构体成员的任何联合体)不得是结构体的成员或数组的元素


8
标准规定,具有灵活数组成员(FAM)的结构的变量部分在计算大小时将被忽略:

作为一种特殊情况,具有多个命名成员的结构的最后一个元素可能具有不完整的数组类型;这称为灵活数组成员。 在大多数情况下,灵活数组成员将被忽略。特别地,结构的大小就像灵活数组成员被省略一样,除了它可能具有比省略所暗示的更多的尾部填充。但是,当一个 . (或->)运算符具有左操作数(指向)具有灵活数组成员的结构,并且右操作数命名该成员时,它的行为就好像该成员被替换为不会使对象访问超出结构体大小的最长数组(具有相同的元素类型);即使这个数组的偏移量与替换数组的偏移量不同,该数组的偏移量仍应保持为灵活数组成员的偏移量。如果这个数组没有元素,它的行为就像它有一个元素,但如果试图访问该元素或生成一个超过它的指针,则行为是未定义的。

强调添加

请注意,struct F_s(又名F_t)不应该被接受;这违反了§6.7.2.1 ¶3中的约束:

结构体或联合体不得包含不完整或函数类型的成员(因此,结构体不得包含自身的实例,但可以包含指向自身实例的指针),除非具有多个命名成员的结构体的最后一个成员可能具有不完整的数组类型;这样的结构体(以及任何包含可能递归地包含该结构体成员的联合体)不得是结构体的成员或数组的元素。

编译器应该拒绝这个(或者至少发出一条诊断信息),因为约束违规需要一条诊断信息。即使编译器没有直接拒绝它,你也不能实际使用嵌入式S_t的FAM,因为F_tdata成员不会移动——结构体元素的偏移量在编译时是固定的。它将实际上使用F_tdata元素,但这不是定义行为。

7

char data[]; 是一个灵活的数组成员,明确保证其大小不会被计算。因为它主要用于 malloc(sizeof(St_t) + n),其中 ndata 数组的大小。

至于内含另一个结构体的 S_t S;,这是无效的 C 代码,因为包含灵活数组成员的结构体必须放置在最外层结构体的末尾,而你没有这样做。所以你的代码在标准 C 中无法编译,在 C 语言范畴之外,不可能假设 out.S.dataout.data 是同一内存,我猜想 GNU C 可能提供非标准扩展的确定性行为,但我不知道有任何这样的保证。


1
GNU = GNU未记录的 - Lundin
我在标准中没有看到任何特定的内容使得 S_t S; 成员无效。 - Ian Abbott
@IanAbbott — 请参见§6.7.2.1 ¶3。这是一个约束条件,因此在结构体中尝试使用S_t S;将违反约束条件,必须进行诊断。 - Jonathan Leffler
@JonathanLeffler 实际上我没有意识到这个要求不仅仅是一个带有柔性数组成员的结构体必须放在最后。约束条件明确指出,无论放在哪里,该结构体都不能成为另一个结构体的成员。 - Lundin
@Lundin:规则既简单又严格。我不知道即将到来的C23标准是否会修改包含FAM的结构体或联合体的规则。 - Jonathan Leffler
@JonathanLeffler 感谢您提供的参考。至少目前似乎仍然可以使用带有柔性数组成员的结构体作为联合体的成员(只要该联合体不是结构体的成员或数组的元素),因此仍然有一些可能定义具有保留空间以容纳柔性数组成员元素的对象。这些限制是有意义的,因为它们避免了在非联合体中出现类似于联合体的行为。 - Ian Abbott

3

由于结构体S_t中的char[]被称为“灵活数组”,这是C语言C99标准引入的一项功能。

这可能会有所帮助:C语言中的灵活数组成员


1
严格来说,它是一个灵活的数组成员,但这只是琐事。你说得对,FAM是在C99中引入的,但这不是为什么FAM的大小被视为零的原因——因为标准规定其大小被视为零。链接的摘要说FAM“最好声明为最后一个成员”,但标准没有选择——它必须是最后一个成员,因为结构的其他成员的布局是固定的。所以,这个链接并没有太大帮助,尽管互联网上可能有更糟糕的链接。 - Jonathan Leffler
@JonathanLeffler 我不明白为什么它应该是最后一个成员,"因为结构的其他成员的布局是固定的";无论在代码中以什么顺序编写成员,数据结构对齐都应该对其进行排序,不是吗?我猜这是由于支持一般情况,但仅凭这个原因就有点奇怪。 - Maurizio Carcassona
@MaurizioCarcassona:编译器可能不会重新排列结构体的成员;它们必须按照指定的顺序出现。这是导致结构体成员之间和末尾出现填充的原因之一。请参见§6.7.2.1 ¶6§6.2.5所述,结构体是由一系列成员组成的类型,其存储按有序序列分配,而联合是由一系列成员组成的类型,其存储重叠。 - Jonathan Leffler
@MaurizioCarcassona:如果您看到我的答案,您还会发现标准规定FAM是结构的最后一个元素之一。这部分原因是结构中的元素都位于结构内的固定位置,因此访问元素的计算很容易。这也是为什么您不能在一个结构中有两个FAM的原因。数组是固定大小的对象;这就是为什么您不能在数组内部或另一个结构内(甚至不是另一个结构的最后一个成员)放置带有FAM的结构的原因。 - Jonathan Leffler
@JonathanLeffler 您说得对,我应该更加小心并练习英语,毕竟这不是我的母语。 - zhoushuang

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接