与具有灵活数组成员的匿名结构联合

Union with anonymous struct with flexible array member

提问人:Some Name 提问时间:6/5/2019 最后编辑:timrauSome Name 更新时间:8/15/2022 访问量:1824

问:

请考虑以下两个示例:

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?
}

演示

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]);
}

演示

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

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

因此,我将措辞解释为,如果 a 包含匿名者作为成员,匿名者的成员将被视为包含 .unionstructstructunion

问题为什么第一个示例编译良好,而不是像第二个示例那样失败?

c struct 语言律师 联盟

评论


答:

6赞 Sourav Ghosh 6/5/2019 #1

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

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

引用 , §6.7.2.1/P18C11

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

可是

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

也就是说,出于访问目的,布局保持不变。看,在你的第一个例子中,你正在访问(和),就好像他们是工会的直接成员一样。ab

澄清一下,

#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
}

评论

0赞 Some Name 6/5/2019
这是出于访问目的。你能扩展一下吗?匿名结构或联合的成员被视为包含结构或联合的成员。进入语义部分,而 s 不能包含灵活数组成员的事实是 constraint。因此,在我看来,我们可以将语义中的事情解释为违反约束,这在我看来有点矛盾......union
0赞 Some Name 6/5/2019
想了一会儿,那是出于访问目的。 绝对有道理。我删除了我之前的评论。
6赞 Jonathan Leffler 6/5/2019 #2

(C11) 标准在 §6.7.2.1 结构和联合说明符 ¶3 中说 — 约束:

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

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

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

§6.7.2.1 ¶18 定义了术语“灵活数组成员”。

顺便说一句,在问题的第一个版本中,第一个示例中的语句访问了数组中未分配的元素——该缺陷已在修订版 2 中得到修复。写入会给出一个大小为 0 的数组。您必须使用动态内存分配(或某些其他机制)来为非空 FAM 分配具有足够空间的联合或结构。 类似的评论也适用于第二个示例。printf()union test test;

一些名字评论中说

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

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

评论

0赞 Some Name 6/5/2019
但是,由于在第一种情况下,结构是匿名的,因此结构的成员应被视为包含联合的成员,从而使联合包含灵活的数组成员。正如我所引用的:匿名结构或工会的成员被认为是包含结构或工会的成员。
0赞 Sourav Ghosh 6/5/2019
@SomeName 刚刚在我的回答中谈到了这个问题。:)
0赞 Jonathan Leffler 6/5/2019
一个区别是 in 的偏移量不能为 0——这与工会的普通成员完全不同。通常,联合的所有成员都从偏移量 0 开始。不过,大多数情况下,这表示给定一个变量,您可以引用 和 。在过去,您必须为结构指定一个名称:并且已经使用或访问联合中结构的元素。这不会影响允许灵活数组成员的位置;仅用于访问它的符号。bunion testunion test u;u.au.bunion test { struct { int a; int b[]; } s; };u.s.au.s.b
0赞 Eric Postpischil 6/5/2019
现在,你看,当你把“必须”放在斜体中时,我们就必须去挖掘C标准,看看它是否正确。如果我将结构与另一个成员一起放入,该成员是一个数以百万计的数组怎么办?然后定义会分配大量内存。那么我可以使用灵活的数组成员吗?union foocharunion foo x
3赞 Jonathan Leffler 6/5/2019
一旦你得到了答案,请不要改变问题——至少不要以使部分答案无效的方式改变。
14赞 John Bollinger 6/5/2019 #3

注:本答复自首次编写以来已作了实质性修改,反映出在答复的原始版本所依据的文件公布后,委员会的立场发生了变化。

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

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

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

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

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

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

评论

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

当然,匿名复合材料中未标记的复合材料始终保持其形状。但是,§6.7.2.1p13的措辞中没有明确说明这一点。措辞已修订为:(着重号后加):

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

有关C18标准和免费提供的草案的链接,请参阅 http://www.iso-9899.info/wiki/The_Standard (pdf)

评论

0赞 Some Name 6/5/2019
你强调的部分是否已经添加到某个草案中?如果是这样,您能提供参考吗?N1570 不包含它。
1赞 rici 6/5/2019
@SomeName。做。
1赞 supercat 6/6/2019
@rici:我认为常识意味着,如果是具有结构类型与成员的并集的地址,则序列 like 应该等价于 ,至少在没有任何(除了)访问或解决其形成和最后使用之间的并集的情况下。我真的想不出地址运算符还有什么用处。然而,gcc 和 clang 都不承认这种等价性。uumTsmT *p = &u->m; p->sm = 123;u->m.sm = 123;pp
1赞 supercat 6/7/2019
在许多情况下,过去的常识会建议编译器应该支持超出标准要求的结构,但编译器编写者将缺乏授权视为打破此类结构的邀请。常识与某些编译器处理联合的方式无关。我认为常识意味着,采用工会成员地址的代码应该能够访问它,至少在编译器可以看到这是对工会左值执行的最后操作的情况下。如果那里不尊重常识......
1赞 supercat 6/7/2019
...我不会指望它在任何地方都受到尊重。