【问题标题】:Why is this initialization of a structure with a flexible array member invalid but valid with an fixed size array member?为什么对具有灵活数组成员的结构的初始化无效但对固定大小的数组成员有效?
【发布时间】:2020-10-27 15:10:56
【问题描述】:

C 标准状态(强调我的):

21 示例2声明后:

struct s { int n; double d[]; };

结构结构s 有一个灵活的数组成员d。 [...]


22 按照上述声明:

struct s t1 = { 0 };         // valid
struct s t2 = { 1, { 4.2 }}; // invalid
t1.n = 4;                    // valid
t1.d[0] = 4.2;               // might be undefined behavior

t2 的初始化无效(并且违反了约束)因为 struct s 被视为不包含成员 d

来源:C18,§6.7.2.1/20 + /21

我不明白“因为struct s被视为不包含成员d”的解释

如果我使用{ 1, { 4.2 }};的初始化器,{ 4.2 }部分就是初始化灵活数组成员; 确切地说,将灵活数组成员初始化为由一个元素组成,并将该元素初始化为值4.2,因此stuct s被视为具有成员d

这句话在我看来毫无意义。

  • 为什么标准说{ 4.2 } 不会初始化/表示灵活数组成员,因此结构会被视为没有成员d

如果我使用固定大小的数组,则此表示法可以正常工作并初始化成员而不会产生任何抱怨:

struct foo {
    int x;
    double y[1];
};

int main (void)
{
    struct foo a = { 1, { 2.3 } };
}

Evidence

  • 为什么这个初始化在结构有一个灵活的数组成员时无效,但在结构有一个固定大小的数组成员时有效?

你能详细说明一下吗?


我读过:

Why does static initialization of flexible array member work?

How to initialize a structure with flexible array member

Flexible array members can lead to undefined behavior?

还有其他人,但他们都没有回答我这句话想解释什么以及为什么这是无效的。


相关:

【问题讨论】:

    标签: c struct initialization language-lawyer flexible-array-member


    【解决方案1】:

    我猜这是语言缺陷。虽然初始化一个灵活的数组成员可能没有意义,但标准需要在某个地方解决这个问题。我在任何地方都找不到这样的规范文本。

    灵活数组成员的定义是,C17 6.7.2.1/18:

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

    由此我们了解到,灵活数组成员是不完整的数组类型。然而,我们不知道在什么情况下灵活数组成员被忽略,除了在计算结构的大小时。 “在大多数情况下”没有帮助并且是缺陷 - 这需要扩展为一个详尽的列表,包括作为初始化列表的一部分的灵活数组成员的行为。否则,人们可能会认为它的行为与任何其他不完整类型的数组一样。

    C17 6.2.5/22:

    未知大小的数组类型是不完整类型。

    然后初始化规则说,C17 6.7.9:

    要初始化的实体的类型应该是一个未知大小的数组或者一个完整的对象类型,不是变长数组类型。

    到目前为止,还没有规范文本说我们不允许为灵活的数组成员提供初始化程序 - 相反。问题中的示例(C17 6.7.2.1 示例 21)不是规范的,因为示例在 ISO 标准中不是规范的。该示例没有提及违反了哪个约束,也没有提及它说必须忽略灵活数组成员的位置。

    我想我可能会就此提交一份 DR。

    【讨论】:

    • 我不知道我们仍然可以发布 DR。我认为委员会暂停了非内部人员的反馈。最近想找做DR,但是找不到机会。
    • @RobertSsupportsMonicaCellio 您可能必须通过您的国家标准组织并通过他们联系 ISO WG。我搜索了DR,但找不到任何东西。这应该是导致引入灵活数组成员的原始版本:open-std.org/jtc1/sc22/wg14/www/docs/n791.htm,自早期提案以来,文本发生了很大变化。
    • 我也对“invalid”的分类存有疑问(尽管不是标准的),因为编译器确实允许这样做,如 GCC 所示。 "invalid" 暗示恕我直言,该示例完全被禁止/无效。应该将其声明为“行为未指定/未定义”,因为它与第四个示例类似。
    【解决方案2】:

    我不明白“因为struct s被视为不包含成员d”的解释。

    C 标准还说“在大多数情况下,灵活的数组成员会被忽略。”不清楚您为什么不理解什么这是什么意思。如果struct s 被声明为struct s { int n; double d[]; };,那么在大多数情况下,C 实现的行为就好像它被声明为struct s { int n; };。因此,struct s t2 = { 1, { 4.2 }}; 失败了,因为 4.2 是一个实际上不存在的东西的初始化器。

    为什么会出现这种情况是明智的。在大多数情况下,我希望编译器可以支持一个定义,其中数组初始值设定项被计算并用于设置结构大小。当然,编译器使用数组定义来做到这一点,例如 s int a[] = { 3, 4, 5};。但是,这不是灵活数组成员的典型用例。通常,程序接收有关它需要使用结构管理多少元素的信息,为结构分配空间,其中包含用于这些元素的空间,然后将结构放入分配的空间中。也就是说,具有灵活数组成员的结构的典型用例是动态分配空间。我希望 C 委员会认为几乎不需要要求编译器支持静态或自动对象中的灵活数组成员,而不是动态对象。

    【讨论】:

    • "该短语没有出现在 C 标准中" - 我可以在 n1124 (C99+T1/T2)、n1570 (C11) 和 n2310 (C18) 中找到它- 即使前者的段落编号不同。虽然它们确实是草案,但我想不到这不会成为标准的一部分。
    • 实际的ISO标准中也有这个例子。
    • @RobertSsupportsMonicaCellio:我搜索了 C 2018 的官方版本和 C 2011 的 n1570 草案中的“包含”(以避免换行或其他格式干扰搜索整个短语的可能性)和在任何一个草案中都没有找到这样的短语。如果您认为它存在于任何一个中,请引用特定的条款和段落编号。如果您使用网络上的资源,请提供其 URL。
    • @EricPostpischil 我在问题中这样做了:C18,§6.7.2.1/20 + /21 - 有问题的文本位于 /21。
    • @RobertSsupportsMonicaCellio:好的,抱歉。我现在正在查看它,但我的 PDF 查看器并未将其显示为热门!我会提交错误报告。
    【解决方案3】:

    您在引用的示例中省略了一些重要的语言 - 这是全文:

    20 例2 声明后:

    struct s { int n; double d[]; };
    struct s 结构有一个灵活的数组成员 d典型的使用方法是:

    int m = /* some value */;
    struct s *p = malloc(sizeof (struct s) + sizeof (double [m]));
    并假设对malloc 的调用成功,p 指向的对象在大多数情况下表现得好像 p 已声明为:

    struct { int n; double d[m]; } *p;
    (在某些情况下,这种等价性被破坏;特别是成员 d 的偏移量可能 不一样)。

    IOW,只有当您动态分配 struct 实例并为数组成员分配额外空间时,灵活的数组成员才能真正发挥作用。

    灵活的数组成员没有大小,因此它不会影响 struct 类型的大小 - 也就是说,sizeof (struct s) 的结果评估为类型的大小没有 数组。

    【讨论】:

    • 一种典型的方式”意味着不是“唯一的方式”。他们没有明确禁止将带有 FAM 的结构用作 static 对象(我知道,您没有说过,但您的回答可能会整体暗示)。他们只将这个特定的(静态)初始化声明为无效,尽管 GCC 仍然允许它作为扩展。我想知道他们为什么这么宣布。同样正如@Lundin 已经暗示的那样,这些例子不是规范的,所以我们不应该只关注那些。
    • 最后一句话也不太正确。没有将 FAM 本身的大小添加到结构的大小是正确的,但它不会影响 struct 类型的大小是不正确的,它会对填充产生影响:“特别是,结构的大小就像省略了灵活数组成员一样,只是它可能具有比省略所暗示的更多的尾随填充。",C18 6.7.2.1/18。
    【解决方案4】:

    IMO 这是因为当结构声明为extern 时,无法在另一个编译单元中确定以这种方式初始化的结构的sizeof

    【讨论】:

      猜你喜欢
      • 2015-03-07
      • 1970-01-01
      • 2019-01-11
      • 2020-04-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多