基本上,我有一个
struct foo { /* variable denoting active member of union */ enum whichmember w; union { struct some_struct my_struct; struct some_struct2 my_struct2; struct some_struct3 my_struct3; /* let's say that my_struct is the largest member */ }; }; main() { /*...*/ /* earlier in main, we get some struct foo d with an */ /* unknown union assignment; d.w is correct, however */ struct foo f; f.my_struct = d.my_struct; /* mystruct isn't necessarily the */ /* active member, but is the biggest */ f.w = d.w; /* code that determines which member is active through f.w */ /* ... */ /* we then access the *correct* member that we just found */ /* say, f.my_struct3 */ f.my_struct3.some_member_not_in_mystruct = /* something */; }
通过指针访问C联盟成员似乎说通过指针访问成员是可以的.看评论.
但我的问题是直接访问它们.基本上,如果我将所需的所有信息写入联盟的最大成员并手动跟踪类型,那么每次访问手动指定的成员仍然会产生正确的信息吗?
我注意到问题中的代码使用匿名联合,这意味着它必须为C11编写; 匿名工会不是C90或C99的一部分.
ISO/IEC 9899:2011,目前的C11标准,有这样的说法:
§6.5.2.3结构和工会成员
3后缀表达式后跟
.
运算符,标识符指定结构或联合对象的成员.该值是指定成员的值,95)如果第一个表达式是左值,则它是左值.如果第一个表达式具有限定类型,则结果具有指定成员类型的限定版本.4后缀表达式后跟
->
运算符,标识符指定结构或联合对象的成员.该值是第一个表达式指向的对象的指定成员的值,并且是左值.96)如果第一个表达式是指向限定类型的指针,则结果具有指定成员类型的限定版本.5......
6为了简化联合的使用,我们做了一个特殊的保证:如果一个联合包含几个共享一个共同初始序列的结构(见下文),并且如果联合对象当前包含这些结构中的一个,则允许检查它们中任何一个的共同初始部分,可以看到完整类型的联合声明.如果对应的成员具有一个或多个初始成员的序列的兼容类型(并且对于位字段,具有相同的宽度),则两个结构共享共同的初始序列.
95)如果用于读取union对象内容的成员与上次用于在对象中存储值的成员不同,则该值的对象表示的适当部分将被重新解释为新对象表示如6.2.6所述的类型(有时称为''punning''的过程).这可能是陷阱表示.
96)如果
&E
是有效的指针表达式(其中&
是''address-of''运算符,它生成指向其操作数的指针),则表达式(&E)->MOS
与E.MOS
.
标准中的斜体
并且第6.2.6节的类型表示(部分):
§6.2.6.1总则
6当值存储在结构或联合类型的对象中时,包括在成员对象中,对应于任何填充字节的对象表示的字节采用未指定的值.51)结构或联合对象的值永远不是陷阱表示,即使结构或联合对象的成员的值可能是陷阱表示.
7当值存储在union类型的对象的成员中时,对象表示的字节与该成员不对应但与其他成员对应的字节采用未指定的值.
51)因此,例如,结构分配不需要复制任何填充比特.
我对你正在做的事情的解释是脚注51说"它可能不起作用",因为你可能只分配了部分结构.你充其量只是在冰上行走.但是,对此,您规定分配的结构(在f.my_struct = d.my_struct;
分配中)是最大的成员.机会是中等高度,它不会出错,但如果两个结构中的填充字节(在联合的活动成员和联盟的最大成员中)位于不同的位置,那么事情可能会出错并且如果您向编译器编写者报告了问题,编译器编写者只会对您说"不要违反标准".
因此,就我是语言律师而言,这位语言律师的答案是"不保证".在实践中,你不太可能遇到问题,但可能性存在并且你没有任何人回来.
要使代码安全,只需使用f = d;
联合赋值即可.
假设机器需要double
在8字节边界上对齐sizeof(double) == 8
,并且int
必须在4字节边界上对齐sizeof(int) == 4
,并且short
必须在2字节边界上对齐并且sizeof(short) == 2
).这是一组看似合理,甚至是常见的尺寸和对齐要求.
此外,假设您在问题中具有结构的双结构联合变体:
struct Type_A { char x; double y; }; struct Type_B { int a; short b; short c; }; enum whichmember { TYPE_A, TYPE_B }; struct foo { enum whichmember w; union { struct Type_A s1; struct Type_B s2; }; };
现在,在指定的大小和对齐下,struct Type_A
将占用16个字节,struct Type_B
并将占用8个字节,因此联合也将使用16个字节.联盟的布局将是这样的:
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | x | p...a...d...d...i...n...g | y | s1 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | a | b | c | p...a...d...d...i...n...g | s2 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
该w
元素还意味着struct foo
在(匿名)联合之前有8个字节,其中很可能w
只占4个.struct foo
因此该机器上的大小为24.但这与讨论并不特别相关.
现在假设我们有这样的代码:
struct foo d; d.w = TYPE_B; d.s2.a = 1234; d.s2.b = 56; d.s2.c = 78; struct foo f; f.s1 = d.s1; f.w = TYPE_B;
现在,根据脚注51的裁决,结构分配f.s1 = d.s1;
不必复制填充位.我知道没有编译器表现得像这样,但标准说编译器不需要复制填充位.这意味着价值f.s1
可能是:
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | x | g...a...r...b...a...g...e | r...u...b...b...i...s...h | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
垃圾是因为那些7个字节不需要被复制(脚注51说这是一个选项,即使它不太可能是任何当前编译器执行的选项).垃圾是因为初始化d
从不在那些字节中设置任何值; 该部分结构的内容未指定.
如果您现在继续尝试将其f
作为副本处理d
,您可能会惊讶地发现f.s2
实际上只初始化了8个相关字节中的1个字节.
我会再次强调:我知道没有编译器可以做到这一点.但问题是标记为"语言律师",因此问题是"语言标准状态是什么",这是我对标准引用部分的解释.