【问题标题】:Union with anonymous struct with flexible array member与具有灵活数组成员的匿名结构联合
【发布时间】:2019-10-21 01:31:51
【问题描述】:

考虑以下两个例子:

1.

union test{
  struct {
      int a;
      int b[];
  };
};


int main(void){
    union test test;
    test.a = 10;
    printf("test.b[0] = %d", test.b[0]); //prints 0, UB?
}

DEMO

2.

#include <stdio.h>

union test{
    int a;
    int b[]; //error: flexible array member in union
};


int main(void){
    union test test;
    test.a = 10;
    printf("test.b[0] = %d", test.b[0]);
}

DEMO

行为不清楚。我希望这些示例的行为相同(即第一个示例也无法编译),因为6.7.2.1(p13):

匿名结构或联合的成员被认为是 包含结构或联合的成员。

所以我将措辞解释为好像union 包含匿名struct 作为成员,匿名struct 的成员将被视为包含union 的成员。

问题为什么第一个示例编译正常而不是第二个示例失败?

【问题讨论】:

    标签: c struct language-lawyer union


    【解决方案1】:

    注意:此答案自首次编写以来已进行了实质性修改,反映了在发布答案的原始版本所依赖的文件后委员会立场发生了变化。

    匿名结构或联合的成员被视为包含结构或联合的成员。

    这是一个很难解释的条款,事实上它已经成为至少两个违反标准的缺陷报告的主题。正如委员会在其对DR 499 的回复中所支持的那样,其意图是出于布局目的将匿名结构视为结构本身是包含结构或联合的成员,但对其成员的访问是表示 就好像它们是包含结构或联合的成员一样。

    另一方面,DR 502 的公认立场认为,即使是包含灵活数组成员作为其 only 成员的匿名结构,如果它是包含它的结构(不是联合),并且至少有一个在它之前。

    我觉得这些有点不一致,但它们之间的统一主题似乎是该领域标准的意图归结为布局。匿名结构内的灵活数组成员是允许的,只要它位于最里面的命名结构或联合的布局的末尾,考虑到其他成员的考虑,它必须具有非零大小,考虑到成员的事实无论匿名结构是否出现在联合体中,匿名结构的结构都不会重叠。

    委员会对DR 502(与其最初的立场不同)的回应与此一致。它认为结构或联合中的匿名结构必须遵守与其他结构有关灵活数组成员的相同规则,尽管您询问了哪些规定。

    委员会似乎尚未决定您提出的具体问题,但其决定的主题似乎很明确:“被视为包含结构或工会的成员”的措辞旨在狭义地解释为声明仅关于访问匿名结构和联合成员的语法。因此,该条款没有说明匿名结构是否可能包含 FAM,以及关于它们何时何地适用的一般规则。这些规则允许您的第一个案例。

    【讨论】:

    • 根据 2018 年 4 月的会议记录 (n2239.pdf),DR 502 显然并未真正被接受。我在这里发现有趣的是 DR502 中允许struct { int i; struct { int a[]; }; }; 的逻辑也将允许struct { int i; union { int a[]; };}; (IMO)。在实践中,clang 和 gcc 似乎都不允许其中任何一个。 (我今天注意到了这一点,因为我试图从包含 struct { ...; union { char* c; Node* n[0]; }; }; 的标头中删除 0,由于 gcc 扩展,gcc 和 clang 似乎都对此感到满意。)
    • 这里是 DR502 已关闭的更好参考:open-std.org/jtc1/sc22/wg14/www/docs/n2257.htm#dr_502。所以我想我在这里不走运。根据该决议,“效果很容易达到”。我认为对我来说简单的解决方案是将 0 更改为 1。
    • 谢谢@rici,委员会似乎在 2016 年 10 月至 2017 年 4 月之间改变了路线。这在我所依赖的文件(以及我链接的文件)中并不明显,但从你的链接中可以清楚地看到。我会尽快修改答案。
    【解决方案2】:

    第二种情况编译失败,因为灵活数组成员是结构类型的属性,不适用于联合。这很简单。

    接下来,在第一种情况下,尝试访问 b[0] 将是未定义的行为,因为没有为此分配内存。

    引用C11,§6.7.2.1/P18

    作为一种特殊情况,具有多个命名成员的结构的最后一个元素可能 数组类型不完整;这称为灵活数组成员。 [...] 如果这个数组没有元素,它的行为就像 它有一个元素,但如果尝试访问该元素,则行为未定义 元素或生成一个越过它的指针。

    也就是说,

    匿名结构或联合的成员被视为包含结构或联合的成员。

    即出于访问目的,布局保持不变。请看,在您的第一个示例中,您正在访问 a(和 b),就好像它们是工会的直接成员一样。

    为了澄清,

    #include <stdio.h>
    
    union test{
        struct {
            int p;
            float q;
        } t;                //named structure member
      struct {
          int a;
          int b[];
      };
        char pqr;
    };
    
    
    int main(void){
        union test test;
        test.t.p = 20;   // you have to use the structure member name to access the elements
        test.pqr = 'c';     // direct access, as member of union
        test.a = 10;        // member of anonymous structure, so it behaves as if direct member of union
    }
    

    【讨论】:

    • 这是为了访问目的。你能扩展一下吗? 匿名结构或联合的成员被认为是包含结构或联合的成员。进入语义部分,而unions 不能包含灵活数组成员的事实是约束。所以在我看来,我们可以将 Semantic 中的事物解释为违反约束……
    • 想了想那是为了访问的目的。绝对有道理。我删除了我之前的评论。
    【解决方案3】:

    (C11) 标准在 §6.7.2.1 Structure and union specifiers ¶3 中说——一个约束:

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

    请注意,只有结构可以(直接)包含灵活的数组成员——联合不能。

    第一种情况是合法的。第二个不是。

    (定义术语灵活数组成员的是§6.7.2.1 ¶18。)

    顺便说一句,在问题的第一个 version 中,第一个示例中的 printf() 语句正在访问未分配的数组元素——该缺陷已在修订版 2 中得到修复。编写 @987654328 @ 为您提供一个大小为 0 的数组。您必须使用动态内存分配(或其他一些 mechanism)来为非空 FAM 分配具有足够空间的联合或结构。类似的 cmets 也适用于第二个示例。

    Some Namecomment 中表示

    但由于在第一种情况下结构是匿名的,因此结构的成员应被视为包含联合的成员,从而使联合包含灵活的数组成员。正如我所引用的匿名结构或联合的成员被认为是包含结构或联合的成员。

    请注意,匿名结构不会因为嵌入到联合中而失去其形状。一个区别是bunion test 中的偏移量不能为0——这与普通的联合成员完全不同。通常,联合的所有成员都从偏移量 0 开始。不过,大多数情况下,这表示给定变量union test u;,您可以参考u.au.b。在过去,您必须为结构指定一个名称:union test { struct { int a; int b[]; } s; };,并使用u.s.au.s.b 来访问联合中结构的元素。这不会影响允许灵活数组成员的位置;只有用于访问它的符号。

    【讨论】:

    • 但是由于在第一种情况下结构是匿名的,因此结构的成员应被视为包含联合的成员,从而使联合包含灵活的数组成员。正如我所引用的,匿名结构或联合的成员被视为包含结构或联合的成员。
    • @SomeName 刚刚在我的回答中解决了这个问题。 :)
    • 一个区别是bunion test中的偏移量不能为0——这与普通的联合成员完全不同。通常,联合的所有成员都从偏移量 0 开始。不过,大多数情况下,这表示给定变量union test u;,您可以参考u.au.b。在过去,您必须为结构指定一个名称:union test { struct { int a; int b[]; } s; };,并使用u.s.au.s.b 来访问联合中结构的元素。这不会影响允许灵活数组成员的位置;只有用于访问它的符号。
    • 现在,你看,当你把“must”写成这样的斜体字时,我们必须去挖掘 C 标准,看看它是否正确。如果我将结构与另一个成员放在union foo 中,该成员是一个由无数char 组成的数组,该怎么办?然后定义union foo x 分配大量内存。那我可以使用灵活数组成员吗?
    • 一旦你得到答案,请不要更改问题 - 至少不要以使部分答案无效的方式。
    【解决方案4】:

    毫无疑问,匿名复合材料中的未标记复合材料始终保持其形状。但这在 §6.7.2.1p13 的措辞中并没有明确说明。在 C18 中将措辞修改为:(添加了重点):

    1. 类型说明符是没有标记的结构说明符的未命名成员称为匿名结构;类型说明符是没有标记的联合说明符的未命名成员称为匿名联合。匿名结构或联合的成员被视为包含结构或联合的成员,保持其结构或联合布局。如果包含结构或联合也是匿名的,这将递归地应用。

    有关 C18 标准的链接,请参阅 http://www.iso-9899.info/wiki/The_Standardfreely-available draft (pdf)

    【讨论】:

    • 您强调的部分是否已经添加到某些草稿中?如果可以,可以给个参考吗? N1570 不包含它。
    • @SomeName。完成。
    • @rici:我认为常识会暗示,如果u 是结构类型为T 的成员um 与成员sm 的联合的地址,则类似@ 的序列987654327@ 应该等价于u-&gt;m.sm = 123;,至少在没有任何东西(p 除外)访问或处理p 的形成与其最后使用之间的联合的情况下。我真的想不出地址操作符还有什么用处。然而,gcc 和 clang 都不承认这种等价性。
    • 在许多情况下,过去的常识表明编译器应该支持超出标准要求的结构,但编译器编写者将缺乏授权视为破坏此类结构的邀请.一些编译器对待联合的方式没有常识。我认为常识意味着,至少在编译器可以看到这是使用联合左值完成的最后一个操作的情况下,获取联合成员地址的代码应该能够访问它。如果那里不尊重常识......
    • ...我不会指望它在任何地方都受到尊重。
    猜你喜欢
    • 1970-01-01
    • 2020-04-13
    • 2012-11-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多