具有灵活数组成员的 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;)

推荐答案

如问题所述,C 2018 6.78.2.1 18表示:

…在大多数情况下,灵活数组成员会被忽略…

我们可以认为这忽略了灵活的数组成员,除非另有说明或需要这样做.(对于后者,我正在考虑路由要求.该标准明确指出,具有柔性数组部件的 struct 可能比没有柔性数组部件的 struct 具有更多的尾部填充,但忽略了它可能具有更大的对齐要求这一事实.但显然,如果柔性数组部件的元件比 struct 的其他部件具有更大的要求,则其可能施加更大的对准要求).

由于为了确定兼容性,没有规定忽略柔性数组成员的例外,所以我们应该为了确定兼容性而忽略柔性数组成员(但不能忽略其额外的填充和对齐要求).然后,应用6.7.2.1 1中的规则,我们可以看到问题中的两个struct s个声明没有"其成员之间的一对一对应关系",因为一个声明的末尾有一个数组成员,而另一个声明在我们忽略灵活的数组成员时没有.

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

上述从人类不完美地书写的单词中获取含义的try 留下了这样一种可能性,即具有柔性数组成员的 struct 类型将被认为与没有具有相同对齐要求(并因此具有相同尾部填充)的柔性数组成员的 struct 类型是兼容的.这可能是无意的结果,但可能不会造成任何问题-这两种类型只有在单独的翻译单元中声明时才被认为是兼容的,并且在赋值和其他操作中的行为相同,只是灵活数组成员将在一个翻译单元中可访问,而在另一个翻译单元中不可访问.

C++相关问答推荐

获取二维数组的最大元素

初始化char数组-用于初始化数组的字符串是否除了存储数组的位置之外单独存储在内存中

当打印字符串时,为什么在c中没有使用常量限定符时我会收到警告?

括号中的堆栈实现错误问题

Clang:如何强制运行时错误的崩溃/异常由于-fsanitize=undefined

为什么双精度d=flt_max+flt_max;在c语言中得到inf的结果

#定义SSL_CONNECTION_NO_CONST

Boyer Moore算法的简单版本中的未定义行为

C:Assignment中的链表赋值从指针目标类型中丢弃‘const’限定符

将 struct 数组写入二进制文件时发生Valgrind错误

从C中的函数返回静态字符串是不是一种糟糕的做法?

Valgrind用net_pton()抱怨

OMP并行嵌套循环

在C中,为什么这个带有递增整数的main函数从不因溢出而崩溃?

如何在C中计算包含递增和递减运算符的逻辑表达式?

将不同类型的指针传递给函数(C)

gdb - 你能找到持有内部 glibc 锁的线程吗?

无法理解 fgets 输出

如何修复数组数据与列标题未对齐的问题?

为什么 C 字符串并不总是等同于字符数组?