这个 C 程序是否定义了两个结构体定义,涉及一个灵活的数组成员?

Pas*_*uoq 9 c language-lawyer flexible-array-member c17

具有灵活数组成员的结构是一种可以声明变量并sizeof可以应用变量的类型,这一事实会导致以下程序中出现异常行为。

\n

文件fam1.c

\n
#include <stdio.h>\n#include <stddef.h>\n\nstruct s {\n  char c;\n  char t[]; };\n\nextern struct s x;\n\nsize_t s_of_x(void);\n\nint main(void) {\n  printf("size of x: %zu\\n", sizeof x);\n  printf("size of x: %zu\\n", s_of_x());\n}\n
Run Code Online (Sandbox Code Playgroud)\n

文件fam2.c

\n
#include <stddef.h>\n\nstruct s {\n  char c;\n  char t[2]; };\n\nstruct s x;\n\nsize_t s_of_x(void) {\n  return sizeof x;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

该程序在编译和运行时会发出一些令人惊讶的输出:

\n
#include <stdio.h>\n#include <stddef.h>\n\nstruct s {\n  char c;\n  char t[]; };\n\nextern struct s x;\n\nsize_t s_of_x(void);\n\nint main(void) {\n  printf("size of x: %zu\\n", sizeof x);\n  printf("size of x: %zu\\n", s_of_x());\n}\n
Run Code Online (Sandbox Code Playgroud)\n

请注意,您还可以将 \xe2\x80\x9c extern\xe2\x80\x9d 移动到 fam2.c,这会使程序在x.t访问时出现意外行为。需要明确的是,我不知道根据 C17 标准,这样的变体是否会减少定义,但我很确定大多数编译器会生成目标文件,这些目标文件在链接在一起时会产生功能失调的二进制文件。

\n

我不确定 C17 标准的意图是否是使由 fam1.c 和 fam2.c 组成的程序未定义,但我看不出其中的哪些条款使之如此。人们可能会想到 C17 的条款 6.2.7:1 和 6.2.7:2,但如果你仔细阅读它们,它们似乎完全允许 ​​fam1.c 和 fam2.c 正在做的事情:

\n
\n

6.2.7 兼容型和复合型

\n

6.2.7:1 如果两个类型的类型相同,则它们具有兼容类型。用于确定两种类型是否兼容的其他规则\n在类型说明符的 6.7.2 中、类型限定符的 6.7.3 中和声明符的 6.7.6 中描述。55) 此外,两个结构、联合或\n枚举类型在单独的翻译单元中声明的\n如果它们的标记和成员满足以下要求,则\n是兼容的:如果一个\n使用标记进行声明,则另一个应使用相同的标记进行声明。\n如果两者都在各自的翻译单元内的任何位置完成\n,那么以下附加要求适用:\n其成员之间应有一一对应关系,以便每对对应的成员都声明为兼容的类型;如果该对的一个成员是用对齐说明符声明的,则另一个成员是用等效的对齐说明符声明的;如果该对中的一个成员使用名称声明,则另一个成员也使用相同的名称声明。对于两个结构体,相应的成员应以相同的顺序声明。对于两个结构或联合,\n相应的位字段应具有相同的宽度。对于两个枚举,相应的成员应具有相同的值。

\n

6.2.7:2 引用同一对象或函数的所有声明应具有兼容的类型;否则,行为是未定义的。

\n
\n

作为参考,灵活数组成员在6.7.2.1:18中描述:

\n
\n

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

\n
\n

我是否在 6.2.7 或 C17 的其他地方遗漏了某些内容,导致 fam1.c+fam2.c 未定义?或者它是根据 C17 标准定义的 C 程序,在这种情况下,extern结构体的非 FAM 版本上的变体x.t是否出于相同原因在同一编译单元中访问?

\n

(这是一个题外话,但我想我可以解释为什么 6.2.7:1 是这样写的。其意图可能是允许struct s { int (*m)[]; } x;在一个编译单元和struct s { int (*m)[2]; } x;另一个编译单元中)

\n

Eri*_*hil 7

正如问题中所述,C 2018 6.78.2.1 18 说:

\n
\n

\xe2\x80\xa6 大多数情况下,灵活数组成员被忽略\xe2\x80\xa6

\n
\n

我们可以认为这忽略了柔性阵列成员,除非另有说明或必要性规定。(对于后者,我正在考虑对齐要求。该标准明确表示具有灵活数组成员的结构可能比没有灵活数组成员的结构具有更多的尾部填充,但忽略了它可能具有更大对齐要求的事实。但显然,如果灵活数组成员的元素比结构的其他成员有更大的要求,则它可能会施加更大的对齐要求。)

\n

由于为了确定兼容性,没有规定忽略灵活数组成员的例外情况,因此我们应该为了确定兼容性而忽略灵活数组成员(但不能忽略其额外的填充和对齐要求)。然后,应用 6.7.2.1 1 中的规则,我们看到struct s问题中的两个声明在其成员之间没有 \xe2\x80\x9cone-to-one 对应关系,\xe2\x80\x9d\xc2\xa0since 1末尾有一个数组成员,而当我们忽略灵活数组成员时,另一个则没有。

\n

此外,我认为 6.7.2.1 1 中没有提及潜在的额外填充(以及没有提及额外的对齐要求)作为委员会未能充分考虑灵活数组成员对兼容性声明的影响的证据。因此,6.7.2.1 1 和 6.7.2.1 1 是不完整的。

\n

上述从人类不完美书写的文字中获取意义的尝试留下了这样一种可能性:具有灵活数组成员的结构类型将被视为与没有具有相同对齐要求(因此具有相同尾随要求)的灵活数组成员的结构类型兼容。填充)。这可能是无意的结果,但可能不会导致任何问题\xe2\x80\x94仅当在单独的翻译单元中声明时,两种类型才被视为兼容,并且对于赋值和其他操作,除了灵活数组成员可访问之外,其行为相同在一个翻译单元中而不是在另一个翻译单元中。

\n