通过联合键入C和C ++中的结构
我用gcc和g ++编译了这个迂腐,我不会在任何一个中得到警告:
#include #include #include struct a { struct a *next; int i; }; struct b { struct b *next; int i; }; struct c { int x, x2, x3; union { struct aa; struct bb; } u; }; void foo(struct b *bar) { bar->next->i = 9; return; } int main(int argc, char *argv[]) { struct cc; memset(&c, 0, sizeof c); cuanext = (struct a *)calloc(1, sizeof(struct a)); foo(&c.ub); printf("%d\n", cuanext->i); return 0; }
这在C和C ++中是否合法? 我读过关于打字的类型,但我不明白。 foo(&c.ub)
与foo((struct b *)&c.ua)
什么不同? 它们不一样吗? 联合中结构的这种例外(来自3.3.2.3中的C89)说:
如果联合包含多个共享公共初始序列的结构,并且如果联合对象当前包含这些结构中的一个,则允许检查它们中的任何一个的公共初始部分。 如果相应的成员具有一个或多个初始成员的序列的兼容类型,则两个结构共享共同的初始序列。
在联合中, struct a
的第一个成员是struct a *next
,而struct b
的第一个成员是struct b *next
。 正如您所看到的那样,指向struct a *next
的指针被写入,然后在foo中读取指向struct b *next
的指针。 它们兼容吗? 它们都是指向结构的指针,指向任何结构的指针应该是相同的大小,所以它们应该是兼容的并且布局应该是相同的吗? 从一个结构读取i
并写入另一个结构是否可以? 我是否提交任何类型的别名或类型违规?
在C:
struct a
和struct b
不是兼容的类型。 即使在
typedef struct s1 { int x; } t1, *tp1; typedef struct s2 { int x; } t2, *tp2;
s1
和s2
不兼容。 (参见6.7.8 / p5中的示例。)识别不兼容结构的一种简单方法是,如果两种结构类型兼容,则可以将某种类型的某种类型分配给另一种类型。 如果您希望编译器在尝试执行此操作时进行投诉,则它们不是兼容类型。
因此, struct a *
和struct b *
也是不兼容的类型,因此struct a
和struct b
不共享公共的初始子序列。 在其他情况下,你的工会惩罚取决于工会双关的相同规则(6.5.2.3脚注95):
如果用于读取union对象内容的成员与上次用于在对象中存储值的成员不同,则将值的对象表示的相应部分重新解释为新类型中的对象表示forms6.2.6中描述的过程(有时称为”punning”)。 这可能是陷阱表示。
在C ++中, struct a
和struct b
也不共享一个共同的初始子序列。 [class.mem] / p18(引用N4140):
如果相应的成员具有布局兼容类型并且两个成员都不是位字段,或者两者都是具有相同宽度的位字段(对于一个或多个初始成员的序列),则两个标准布局结构共享共同的初始序列。
[basic.types] / P9:
如果两种类型
T1
和T2
是相同类型,则T1
和T2
是布局兼容类型。 [ 注意 :与布局兼容的枚举在7.2中描述。 与布局兼容的标准布局结构和标准布局联合在9.2中描述。 – 结束说明 ]
struct a *
和struct b *
既不是结构,也不是联合或枚举; 因此,如果它们是相同的类型,它们只是布局兼容的,它们不是。
确实([basic.compound] / p3)
符合cv资格和cv不合格版本(3.9.3)的布局兼容类型的指针应具有相同的值表示和对齐要求(3.11)。
但这并不意味着那些指针类型是布局兼容类型,因为该术语在标准中定义。
你可以做什么(我以前被这个咬过),声明struct的初始指针是void*
并且进行转换。 由于void可以转换为任何指针类型,因此你只会被迫支付丑陋税,并且不会冒险gcc 重新安排你的操作 (我见过这种情况 – 即使你使用了工会),因为某些版本中的编译器错误。 正如@TC正确指出的那样,给定类型的布局兼容性意味着在语言级别它们是可转换的; 即使类型可能偶然具有相同的大小,它们也不一定是布局兼容的; 这可能会让一些贪婪的编译器基于此假设其他一些东西。
我前段时间遇到过类似的问题,我想我可以回答你的问题。
是的, struct a
和struct b
不是兼容的类型,指向它们的指针也是不兼容的。
是的,即使从C89标准的过时观点来看,你所做的也是非法的。 但是,值得注意的是,如果你颠倒struct a
和struct b
中元素的顺序,你将能够访问struct c
实例的int i
(但不能以任何方式访问它的next
指针,即bar->i = 9;
而不是bar->next->i = 9;
),但仅从C89标准的角度来看。
但即使你要颠倒两个struct
中元素的顺序,从C99和C11标准的角度来看,你所做的事情仍然是非法的(由委员会解释)。 在C99中,您引用的标准部分已更改为:
为了简化联合的使用,我们做了一个特殊的保证:如果一个联合包含几个共享一个共同初始序列的结构(见下文),并且如果联合对象当前包含这些结构中的一个,则允许检查公共其中任何一个的初始部分都可以看到完整类型的联合声明 。
最后一个短语有点模棱两可,因为你可以用几种方式解释“可见”,但是,根据委员会的说法,这意味着应该对所讨论的联合类型的对象执行检查。
因此,在您的情况下,处理此问题的正确方法将是:
struct a { int i; struct a *next; }; struct b { int i; struct b *next; }; union un { struct aa; struct bb; }; struct c { int x, x2, x3; union un u; }; /* ... */ void foo(union un *bar) { bar.b->next->i = 9; /* This is the "inspection" operation */ return; } /* ... */ foo(&c.u);
从语言 – 律师的角度来看,这一切都很好而且有趣,但实际上,如果你不对它们应用不同的包装设置,具有相同初始序列的结构将具有相同的布局(在99.9%的例)。 实际上,即使在原始设置中它们也应该具有相同的布局,因为指向struct a
和struct b
的指针应该具有相同的大小。 因此,如果您的编译器在打破严格别名时不会变得讨厌,您可以或多或少地安全地对它们进行类型转换,或者在联合中使用它们,就像您现在使用它们一样。
编辑 :正如@underscore_d在对这个答案的评论中所指出的那样,因为C ++标准中的相应条款没有在“适当的部分中可以看到完整类型的联合声明的任何地方”这一行,它可能是可能的是,C ++标准与C89标准在主题上具有相同的立场。
是的,这很好; 你问题中引用的粗体部分涵盖了这个案例。