und*_*e_d 26 c c++ struct unions type-punning
union通过@ecatmur(/sf/answers/2209049671/)通过引用以下位来讨论关于类型惩罚的大部分未实现或实现定义的性质,关于标准的豁免 -布局structs具有成员类型的"公共初始序列":
C11(6.5.2.3结构和联合成员 ; 语义):
[...]如果一个联合包含几个共享一个共同初始序列的结构(见下文),并且如果联合对象当前包含这些结构中的一个,则允许检查其中任何一个的公共初始部分.完整的工会类型的声明是可见的.如果对应的成员具有一个或多个初始成员的序列的兼容类型(并且对于位字段,具有相同的宽度),则两个结构共享 共同的初始序列.
C++ 03([class.mem]/16):
如果POD-union包含两个或多个共享公共初始序列的POD结构,并且如果POD-union对象当前包含这些POD结构中的一个,则允许检查它们中的任何一个的公共初始部分.如果对应的成员具有一个或多个初始成员的序列的布局兼容类型(并且对于位字段,具有相同的宽度),则两个POD结构共享共同的初始序列.
这两个标准的其他版本有相似的语言; 从C++ 11开始,使用的术语是标准布局而不是POD.
由于不需要重新解释,这不是真正的类型惩罚,只是应用于union成员访问的名称替换.C++ 17(臭名昭着的P0137R1)的提议使得这种语言明确地使用了"访问就像其他结构成员被提名一样"的语言.
但请注意粗体 - " 在任何地方都可以看到完整类型的联合声明 " - C11中存在的条款,但在2003年,2011年或2014年的C++草案中没有任何内容(几乎完全相同,但后来的版本取代了" POD"使用新术语标准布局).在任何情况下,在union任何C++标准的相应部分中都完全没有' 类型位的可见声明.
@loop和@ Mints97,这里 - /sf/answers/1997029261/ - 显示这一行在C89中也没有出现,首先出现在C99中,然后保留在C中(尽管如此,从来没有过滤过到C++).
[剪掉 - 看我的回答]
从那以后,我的问题是:
这是什么意思?什么被归类为"可见声明"?该条款是否旨在缩小 - 或扩大 - 这种"惩罚"定义行为的背景范围?
我们是否假设C++中的这种遗漏是非常慎重的?
C++与C不同的原因是什么?C++是否只是从C89"继承"了这个,然后决定 - 或者更糟,忘记 - 与C99一起更新?
如果差异是故意的,那么C和C++中的两种不同治疗有哪些好处或缺点?
在编译或运行时它有什么有趣的后果?例如,@ ecatmur,在回复我的评论中指出了他的原始答案(如上所述),推测如下.
我认为它允许更积极的优化; C可以假设函数参数
S* s,T* t即使它们共享一个公共的初始序列,只要union { S; T; }在视图中没有,就不要别名,而C++只能在链接时做出这个假设.可能值得问一个关于这种差异的单独问题.
好吧,我在这里,问!我对这方面的任何想法都很感兴趣,特别是:(或者)标准的其他相关部分,委员会成员或其他受尊敬的评论员的引用,开发人员的见解,他们可能已经注意到由此产生的实际差异 - 假设任何编译器甚至困扰执行C'S加入子句-等的目的是产生有关事实的约该C子句及其(有意或无意)由C++遗漏一个有用的目录.那么,我们走吧!
und*_*e_d 17
我已经通过迷宫找到了解决这个问题的方法,我想我已经对它进行了非常全面的总结.我发布这个作为答案,因为它似乎解释了C语句的(IMO非常误导)意图和C++不继承它的事实.如果我发现进一步的支持材料或情况发生变化,这将随着时间的推移而演变.
这是我第一次尝试总结出一个非常复杂的局面,这似乎不明确的,甚至很多语言的建筑师,所以我会欢迎,关于如何提高这个答案澄清/建议 - 或者只是一个更好的答案,如果任何人有一个.
通过隐约相关的线程,我发现下面的答案被@tab -并大加赞赏所包含的链接(照明,如果没有定论),GCC和工作组的缺陷报告:答案通过标签上的StackOverflow
GCC链接包含一些有趣的讨论,并揭示了委员会和编译器供应商的一部分相当大的混淆和相互矛盾的解释 - 围绕C和C++ 中union成员struct,双关语和别名的主题.
最后,我们链接到主要事件 - 另一个BugZilla线程,错误65892,包含一个非常有用的讨论.特别是,我们找到了两个关键文件中的第一个:
C提案N685是关于union类型声明可见性的附加条款的起源.通过一些声称(参见GCC线程#2)对"公共初始序列"容差的完全误解,N685确实旨在允许放宽structTU内的"公共初始序列" 的别名规则,意识到某些union包含的实例所说的struct类型,正如我们从这句话中看到的那样:
建议的解决方案是要求如果通过公共初始序列(如上所述)的别名是可能的,则可以看到联合声明.因此,如果需要,以下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;
}
Run Code Online (Sandbox Code Playgroud)
根据海湾合作委员会的讨论和下面的评论,如@ ecatmur's,这个提议 - 似乎要求推测性地允许任何struct类型的别名,在union这个TU 中有一些可见的实例- 似乎已经受到很大的嘲笑,很少被实施.
显而易见的是,如果没有完全削弱许多优化措施来满足对附加条款的这种解释是多么困难 - 几乎没有什么好处,因为很少有编码人员想要这种保证,而那些做的人只能开启fno-strict-aliasing(IMO表明更大的问题).如果实施,这种限额更有可能吸引人们并与其他声明的虚假互动union,而不是有用.
继之以及我在其他地方发表的评论之后,@ Potatoswatter在这里的答案中指出:
可见性部分是故意从C++中省略的,因为它被广泛认为是荒谬和无法实现的.
换句话说,看起来C++故意避免采用这个附加条款,可能是因为它广泛存在的荒谬性.在要求"记录"引用时,Potatoswatter提供了关于线程参与者的以下关键信息:
那次讨论中的人基本上都是"记录在案".Andrew Pinski是一个铁杆GCC后端人.Martin Sebor是一名活跃的C委员会成员.Jonathan Wakely是一名活跃的C++委员会成员和语言/图书馆实施者.该页面比我能写的任何内容都更具权威性,清晰性和完整性.
Potatoswatter,在相同的SO纱线之上相连,得出结论,C++故意排除这条线,离开指针无需特殊处理(或者在最好的,实现定义的处理)到公共初始序列.他们的待遇是否将在未来具体确定,与其他任何指针相比,还有待观察; 与我下面关于C的最后一节相比.目前,它不是(而且,IMO,这是好的).
因此,从N685的邪恶行......" 铸一边" ......我们又回到了假设指针进入公共初始序列没有特殊的混叠方面.仍然.值得确认的是,没有它,C++中的这一段意味着什么.好吧,上面的第二个GCC线程链接到另一个gem:
C++缺陷1719.该提案已达到 DRWP状态:"DR问题的解决方案反映在当前的工作文件中.工作文件是该标准未来版本的草案" -引用.这是在C++之后的14或者至少在我在这里的最终草案之后(N3797) - 并提出了一个重要的,并且在我看来有启发性地重写了这一段的措辞,如下所示.我正在强调我认为是重要的变化, {这些评论}是我的:
在具有活动成员 的标准布局联合中{"active"表示
union结构类型的实例,而不仅仅是类型}(9.5 [class.union]),T1允许读取 {以前"检查"}非静态数据构件m另一联合成员的结构类型的T2提供m是共同的初始序列的一部分T1和T2.[ 注意:通过非易失性glvalue读取volatile对象具有未定义的行为(7.1.6.1 [dcl.type.cv]). - 尾注]
这似乎澄清旧措辞的含义是:对我来说,它说,任何明确允许中"双关语" union成员structs的公共初始序列,必须做到通过一个实例母公司union -而不是基于的类型structs(例如指向它们的指针传递给某个函数).这个措辞似乎排除了任何其他解释,即 N685.我会说,C会采取这种做法.嘿,说到哪,见下文!
结果是 - 正如@ecatmur和GCC门票所证明的那样 - 这在C++中定义了这样的union成员struct,实际上在C中,受到与任何其他2个官方无关指针相同的严格别名规则的约束.现在可以更清楚地定义能够读取非活动union成员struct的公共初始序列的明确保证,不包括N685针对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++委员会(通过马丁和克拉克)将试图找到共识并敲定措辞,以便标准最终能说出它意味着什么.
我们只能希望!
再次,欢迎所有进一步的想法.
我怀疑这意味着不仅可以通过联合类型,而且可以在联合之外访问这些公共部分.也就是说,假设我们有这个:
union u {
struct s1 m1;
struct s2 m2;
};
Run Code Online (Sandbox Code Playgroud)
现在假设在某个函数中我们有一个struct s1 *p1指针,我们知道这个指针是从m1这种联合的成员中解除的.我们可以将它转换为struct s2 *指针并仍然访问与之相同的成员struct s1.但是在范围的某处,union u必须显示声明.它必须是完整的声明,它通知编译器成员是struct s1和struct s2.
可能的意图是,如果范围中存在这样的类型,则编译器知道struct s1并且struct s2是别名的,因此通过struct s1 *指针的访问被怀疑真正访问a 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
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1474 次 |
| 最近记录: |