首页 文章

union 'punning' structs w /“common initial sequence”:为什么C(99)而不是C规定'visible declaration of the union type'?

提问于
浏览
23

背景

通过 union 讨论关于类型惩罚的大多数未实现定义的性质,通常引用以下位,通过@ecatmur(https://stackoverflow.com/a/31557852/2757035),对具有"common initial sequence"成员类型的标准布局 struct 进行豁免:

C11(6.5.2.3结构和联合成员;语义):[...]如果一个联合包含几个共享一个公共初始序列的结构(见下文),并且如果联合对象当前包含这些结构中的一个,那么它是允许在可以看到完整类型的工会声明的任何地方检查其中任何一个的共同初始部分 . 如果对应的成员具有一个或多个初始成员的序列的兼容类型(并且对于位字段,具有相同的宽度),则两个结构共享共同的初始序列 . C 03([class.mem] / 16):如果POD-union包含两个或多个共享一个公共初始序列的POD结构,并且如果POD-union对象当前包含这些POD结构中的一个,则允许检查其中任何一个的共同初始部分 . 如果对应的成员具有一个或多个初始成员的序列的布局兼容类型(并且对于位字段,具有相同的宽度),则两个POD结构共享共同的初始序列 . 这两个标准的其他版本有相似的语言;从C11开始,使用的术语是标准布局而不是POD .

由于不需要重新解释,这不是真正的类型惩罚,只是应用于 union 成员访问的名称替换 . 针对C 17(臭名昭着的P0137R1)的提议使用了像'the access is as if the other struct member was nominated'这样的语言 .

但请注意粗体 - “ anywhere that a declaration of the completed type of the union is visible " - a clause that exists in C11 but nowhere in C++ drafts for 2003, 2011, or 2014 (all nearly identical, but later versions replace " POD”与新术语标准布局) . 在任何情况下,在任何C标准的相应部分中都完全没有'_806618_类型位的可见声明 .

@loop和@ Mints97,这里 - https://stackoverflow.com/a/28528989/2757035 - 显示此行也是 absent in C89, first appearing in C99 并且从那时起保留在C中(但是,再次,永远不会过滤到C) .

关于此的标准讨论

[剪掉 - 看我的回答]

问题

从那以后,我的问题是:

  • What does this mean? 什么被归类为'visible declaration'?该条款是否旨在缩小 - 或扩大 - 这种'punning'定义行为的背景范围?

  • Are we to assume that this omission in C++ is very deliberate?

  • What is the reason for C++ differing from C? C从C89开始只是'inherit'然后决定 - 或者更糟,忘记 - 与C99一起更新?

  • 如果差异是有意的,那么 what benefits or drawbacks are there to the 2 different treatments in C vs C++?

  • What, if any, interesting ramifications does it have at compile- or runtime? 例如,@ ecatmur,在回复我的评论中指出了他的原始答案(如上所述),推测如下 .

我想它可以进行更积极的优化; C可以假设函数参数S * s和T * t没有别名,即使它们共享一个共同的初始序列,只要没有union {S;吨;在视图中,C只能在链接时进行该假设 . 可能值得问一个关于这种差异的单独问题 .

好吧,我在这里,问!我对这方面的任何想法都很感兴趣,特别是:(或者)标准的其他相关部分,委员会成员或其他受尊敬的评论员的引用,开发人员的见解,他们可能已经注意到由此产生的实际差异 - 假设任何编译器甚至麻烦执行C 's added clause - and etc. The aim is to generate a useful catalogue of relevant facts about this C clause and its (intentional or not) omission from C++. So, let' s去吧!

2 回答

  • 15

    我已经通过迷宫找到了解决这个问题的方法,我想我已经对它进行了非常全面的总结 . 我发布这个作为答案,因为它似乎解释了C语句的(IMO非常误导)意图和C不继承它的事实 . 如果我发现进一步的支持材料或情况发生变化,这将随着时间的推移而演变 .

    这是我第一次尝试总结一个非常复杂的情况,即使对于许多语言架构师来说这似乎也是不明确的,所以我欢迎有关如何改进这个答案的澄清/建议 - 或者只是一个更好的答案,如果有人有 .

    最后,一些具体的评论

    通过模糊相关的线程,我找到了@tab的以下答案 - 非常感谢所包含的链接(如果不是结论性的话)GCC和工作组缺陷报告:answer by tab on StackOverflow

    GCC链接包含一些有趣的讨论,并揭示了委员会和编制者的一部分相当大的混淆和相互矛盾的解释供应商 - 围绕 union 成员 struct 的主题,在C和C中产生冲突和别名 .

    最后,我们链接到主事件 - 另一个BugZilla线程,Bug 65892,包含 extremely 有用的讨论 . 特别是,我们找到了两个关键文件中的第一个:

    C99中添加行的原点

    C proposal N685是有关 union 类型声明可见性的已添加子句的来源 . 通过一些声称(见GCC线程#2)对"common initial sequence"津贴的完全误解,N685确实是 intended to allow relaxation of aliasing rules for "common initial sequence" structs within a TU aware of some union containing instances of said struct types ,正如我们从这句话中看到的那样:

    建议的解决方案是要求如果可以通过公共初始序列(如上所述)使用别名,则可以看到联合声明 . 因此,如果需要,以下TU提供这种别名:

    union utag {
        struct tag1 { int m1; double d2; } st1;
        struct tag2 { int m1; char c2; } st2;
    };
    
    int similar_func(struct tag1 *pst2, struct tag2 *pst3) {
         pst2->m1 = 2;
         pst3->m1 = 0;   /* might be an alias for pst2->m1 */
         return pst2->m1;
    }
    

    根据海湾合作委员会的讨论和下面的评论,例如@ ecatmur,这个提议 - 似乎要求推测性地允许任何 struct 类型的别名,在某些 union 中有一些实例可见到这个TU - seems to have received great derision and rarely been implemented .

    显而易见的是,如果没有完全削弱许多优化措施来满足对附加条款的这种解释是多么困难 - 几乎没有什么好处,因为很少有编码人员想要这种保证,而那些做的人只能打开 fno-strict-aliasing (IMO表示更大的问题) . 如果实施的话,这个限额更有可能让人们 grab 并与其他声明 union 进行虚假交互,而不是有用 .

    从C中省略该行

    继此之后以及我在其他地方发表的评论中,@Potatoswatter in this answer here on SO指出:

    可见部分故意从C中省略,因为它被广泛认为是荒谬和无法实现的 .

    换句话说, it looks like C++ deliberately avoided adopting this added clause, likely due to its widely pereceived absurdity. 在询问"on the record"引用时,Potatoswatter提供了有关线程参与者的以下关键信息:

    那次讨论中的人基本上都是“记录在案” . Andrew Pinski是一个铁杆GCC后端人 . Martin Sebor是一名活跃的C委员会成员 . Jonathan Wakely是一名活跃的C委员会成员和语言/图书馆实施者 . 该页面比我能写的任何内容都更具权威性,清晰性和完整性 .

    Potatoswatter在上面链接的相同SO线程中得出结论,C故意排除了这一行,没有对指向公共初始序列的指针进行特殊处理(或者,最好是实现定义的处理) . 他们的待遇是否将在未来具体确定,与其他任何指针相比,还有待观察;与我下面关于C的最后一节相比 . 目前,它不是(而且,IMO,这是好的) .

    这对C和实际C实现意味着什么?

    因此,使用来自N685的邪恶线...'抛开'... we'重新回到假设指向公共初始序列的指针在别名方面并不特别 . 仍然 . 值得确认的是,如果没有它,C中的这一段意味着什么 . 好吧,上面的第二个GCC线程链接到另一个gem:

    C++ defect 1719 . 此提案已达到 DRWP 状态:"A DR issue whose resolution is reflected in the current Working Paper. The Working Paper is a draft for a future version of the Standard" - cite . 这是在C 14之后或者至少在我在这里的最终草案之后(N3797) - 并提出了 significant, and in my opinion illuminating, rewrite of this paragraph's wording ,如下所示 . 我正在强调我认为是重要的变化,{这些评论}是我的:

    在具有活动成员的标准布局联合中{“active”表示结构类型T1的联合实例,而不仅仅是类型}(9.5 [class.union]),允许读取{以前“检查”}非结构结构类型T2的另一个联合成员的静态数据成员m提供m是T1和T2的公共初始序列的一部分 . [注意:通过非易失性glvalue读取volatile对象具有未定义的行为(7.1.6.1 [dcl.type.cv]) . - 尾注]

    这似乎澄清了旧措辞的含义:对我而言,它表示任何特别允许 'punning' among union member structs with common initial sequences must be done via an instance of the parent union - 而不是基于 structs 的类型(例如指向它们的指针传递给某个函数) . 这个措辞似乎排除了任何其他解释,即N685 . 我会说,C会采取这种做法 . 嘿,说到哪,见下文!

    结果是 - 正如@ecatmur和GCC门票所证明的那样 - 这留下了 such union member structs by definition in C++, and practically in C, subject to the same strict aliasing rules as any other 2 officially unrelated pointers. 能够读取非活动的共同初始序列 union 成员 struct 的明确保证现在更加明确,不包括模糊和难以想象的单调乏味 - 执行"visibility" asN685尝试用C语言 . 根据这个定义,主编译器的行为与C一样 . 至于C?

    C中的这条线的可能反转/ C中的澄清

    同样非常值得注意的是,C委员会成员Martin Sebor也希望用这种优秀的语言来解决这个问题:

    Martin Sebor 2015-04-27 14:57:16 UTC如果你们中的一个人可以用它来解释这个问题,我愿意写一篇论文并将其提交给WG14,并要求更改标准 . Martin Sebor 2015-05-13 16:02:41 UTC我上周有机会与Clark Nelson讨论这个问题 . Clark过去曾致力于改进C规范的混叠部分,例如在N1520(http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1520.htm) . 他同意,就像N1520中指出的问题一样,这也是一个值得WG14重新审视和修复的突出问题 . “

    Potatoswatter鼓舞人心地总结道:

    C和C委员会(通过马丁和克拉克)将试图找到共识并敲定措辞,以便标准最终可以说出它意味着什么 .

    我们只能希望!

    再次,欢迎所有进一步的想法 .

  • 6

    我怀疑这意味着不仅可以通过联合类型,而且可以在联合之外访问这些公共部分 . 也就是说,假设我们有这个:

    union u {
      struct s1 m1;
      struct s2 m2;
    };
    

    现在假设在某个函数中我们有一个 struct s1 *p1 指针,我们知道这个指针是从这种联合的 m1 成员中解除的 . 我们可以将它转换为 struct s2 * 指针,并仍然可以访问与 struct s1 共有的成员 . 但在范围的某处,必须显示 union u 的声明 . 它必须是完整的声明,它通知编译器成员是 struct s1struct s2 .

    可能的意图是,如果范围中存在这样的类型,则编译器知道 struct s1struct s2 是别名,因此怀疑通过 struct s1 * 指针的访问真正访问 struct s2 ,反之亦然 .

    如果没有任何可见的联合类型以这种方式连接这些类型,就没有这样的知识;可以应用严格别名 .

    由于C中没有措辞,那么为了利用该语言中的“共同初始成员放松”规则,您必须通过联合类型路由访问,如通常所做的那样:

    union u *ptr_any;
    // ...
    ptr_any->m1.common_initial_member = 42;
    fun(ptr_any->m2.common_initial_member);  // pass 42 to fun
    

相关问题