具有灵活数组成员的 struct 是一种可以声明变量并且可以对其应用sizeof
的类型,这一事实导致了以下程序中的异常行为.
文件fam1.c
:文件fam1.c
:
#include <stdio.h>
#include <stddef.h>
struct s {
char c;
char t[]; };
extern struct s x;
size_t s_of_x(void);
int main(void) {
printf("size of x: %zu\n", sizeof x);
printf("size of x: %zu\n", s_of_x());
}
文件fam2.c
:文件fam2.c
:
#include <stddef.h>
struct s {
char c;
char t[2]; };
struct s x;
size_t s_of_x(void) {
return sizeof x;
}
这个程序在编译和运行时会产生一些令人惊讶的输出:
$ clang -std=c17 -pedantic -Wall fam1.c fam2.c
$ ./a.out
size of x: 1
size of x: 3
请注意,您还可以将"extern
"移到Fam2.c,这会使程序在访问x.t
时出现意外行为方面变得更糟.要明确的是,我不知道这样的变体是否会根据C17标准定义得较少,但我非常确定,大多数编译器会生成目标文件,当这些文件链接在一起时,会产生功能失调的二进制文件.
我不确定C17标准的意图是不是使由Fam1.c和Fam2.c组成的程序未定义,但我看不出其中的哪些条款使其成为未定义的.人们可能会想到C17的clauses 6.2.7:1 and 6.2.7:2,但如果你仔细阅读它们,你会发现它们似乎完全允许Fam1.c和Fam2.c正在做的事情:
6.2.7兼容型和复合型
6.2.7:1如果两种类型的类型相同,则两种类型为兼容类型.确定两种类型是否兼容的其他规则包括 对于类型说明符,在6.7.2中描述;对于类型限定符,在6.7.3中描述 以及在6.7.6中针对声明者.55)此外,两个 struct 、联合或 在不同的翻译单元中声明的枚举类型是兼容的 如果它们的标记和成员满足以下要求:如果 是用标记声明的,另一个应该用相同的标记声明. 如果两者都在各自翻译范围内的任何位置完成 单位,则适用以下附加要求: 是其成员之间的一对一通信,使得每个成员 使用兼容的类型声明一对对应的成员;如果 对中的一个成员用对齐说明符声明,即 Other是用等效的对齐说明符声明的;如果 该对的成员用名称声明,另一个用名称声明 同名同姓.对于两个 struct ,对应的构件应为 以同样的顺序宣布.对于两个 struct 或联合, 对应的位域应具有相同的宽度.两个人 枚举时,对应的成员应具有相同的值.
6.2.7:2所有引用同一对象或函数的声明都必须具有兼容的类型,否则行为未定义.
作为参考,灵活的数组成员在6.7.2.1:18中描述:
6.7.2.1:18作为特例, struct 的最后一个元素具有多个 命名成员可能具有不完整的数组类型;这称为 灵活的数组成员.在大多数情况下,灵活的数组成员 被忽略.特别是,该 struct 的大小就好像 灵活数组成员被省略,除非它可能有更多 尾部填充比省略所暗示的要多.然而,当一个. (或-&>)运算符的左操作数是(指向) struct 的指针 使用灵活的数组成员和正确的操作数命名该成员, 它的行为就像将该成员替换为最长的数组一样 (具有相同的元素类型),这不会使 struct 更大 数组的偏移量应保持不变 灵活数组成员的属性,即使该属性与该属性不同 替换数组的.如果此数组没有元素,则它 行为就像它只有一个元素一样,但行为是未定义的(如果有的话) 试图访问该元素或生成指针1 过go 了.
在6.2.7或C17中的其他地方,我是不是遗漏了一些东西,使得Fam1.c+Fam2.c没有定义?或者它是根据C17标准定义的C程序,在这种情况下,extern
在 struct 的非FAM版本上,x.t
在相同的编译单元中被访问的变体是出于相同的原因定义的吗?
(这是一个题外话,但我想我可以解释为什么6.2.7:1是这样写的.例如,意图可能允许在一个编译单元中使用struct s { int (*m)[]; } x;
,在另一个编译单元中使用struct s { int (*m)[2]; } x;
)