通过this question系统提示:
所述C11 standard指出一个指向联合可以被转换为一个指针到它的每一个构件。从第6.7.2.1p17:
工会的大小足以容纳最大的成员。成员的至多一个的值可以被存储在任何时间联合对象。一个指向联合对象,适当地转换,指向每个成员(或如果一个构件是一个位字段,然后到单元在其中驻留),并且反之亦然。
这意味着你可以做到以下几点:
union u {
int a;
double b;
};
union u myunion;
int *i = (int *)&u;
double *d = (double *)&u;
u.a = 2;
printf("*i=%d\n", *i);
u.b = 3.5;
printf("*d=%f\n", *d);
但对于反向:在以上工会的情况下,可以在int *
或double *
安全地转换为union u *
?考虑下面的代码:
#include <stdio.h>
union u {
int a;
double b;
};
void f(int isint, union u *p)
{
if (isint) {
printf("int value=%d\n", p->a);
} else {
printf("double value=%f\n", p->b);
}
}
int main()
{
int a = 3;
double b = 8.25;
f(1, (union u *)&a);
f(0, (union u *)&b);
return 0;
}
在这个例子中,指针int
和double
,这两者都是union u
的成员,被传递到其中union u *
预期的功能。一个标志传递给函数把这件事告诉其“成员”访问。
假设,如在这种情况下,访问的构件实际上是传入对象的类型相匹配,是上述代码法律?
我编这对GCC 6.3.0既-O0
和-O3
无一不得到所需要的输出:
int value=3
double value=8.250000
关于严格走样,不存在从指针到类型(例如&a
)去的问题,到指针到联合含有该类型。这是例外的严格别名规则之一,C17 6.5 / 7:
对象应具有其存储的值仅由具有以下类型中的一个的左值表达式获得: - 与有效类型的对象的类型兼容,/ - / - 包括其成员之间的上述类型的一个集合体或联合类型
所以这是好的,只要严格别名去,只要union
包含int
/ double
。而在自身的指针转换是太明确。
当您尝试访问的内容,例如int
的内容作为一个更大的double
的问题就来了。这可能是UB多种原因 - 我至少能想到C17 6.3.2.3/7的:
一个指向对象类型可被转换成一个指针到一个不同的对象类型。如果所得到的指针不正确aligned69)用于该被引用类型,行为是未定义的。
其中非规范的脚注提供了更多信息:
69)在一般情况下,概念“正确对齐”是可传递的:如果一个指针到类型A被正确地对准,用于一个指针到类型B,而这又被正确地用于指针对准键入C,然后一个指针到A型是对于一个指针类型C.正确对准
在这个例子中,指针int和double,这两者都是工会ü成员,被传递到工会U *预期的功能。一个标志传递给函数把这件事告诉其“成员”访问。
假设,如在这种情况下,访问的构件实际上是传入对象的类型相匹配,是上述代码法律?
你似乎要关注你的分析,相对于的类型而工会成员的严格别名规则。然而,由于
union a_union {
int member;
// ...
} my_union, *my_union_pointer;
,我会倾向于认为形式my_union.member
的表达式和my_union_pointer->member
表达访问类型union a_union
的对象的所存储的值在除了访问该成员的类型的对象。因此,如果my_union_pointer
实际上并不指向一个对象,其有效的类型是union a_union
那么确实是有严格走样规则的违反 - 相对于键入union a_union
- 和行为是不确定的,因此。
该标准给出了使用成员类型的左值访问struct
或union
对象没有一般许可,也没有 - 到目前为止我可以告诉 - 它给进行此类访问任何特定的权限,除非该成员恰好是字符型。它也没有确定由铸造int*
成union u*
的行为可以创建一个其中不存在任何手段。取而代之的是,将永远作为union u
访问的任何存储的创建意味着同时创建该存储内的union u
对象。
相反,标准(从C11草案N1570报价参考)依靠实现以应用脚注88(这个列表的目的是,以指定的对象可能会或可能不会被混淆的情况。),并认识到“严当对象是一个函数或循环[即一些特定执行过程中通过其自身的类型和另一种类型的一个看似无关的左值的左值引用的两个混叠规则”(6.5p7)应只应用于当对象别名一些其他左值]。
当两个左值可以被视为“看似无关的”,当一个实现应该期望识别它们之间的关系,是实现问题的质量问题。锵和gcc似乎认识到左值与unionPtr->value
和unionPtr->value[index]
是*unionPtr
相关的形式,但似乎无法识别指向这样的左值有任何关系unionPtr
。因此,他们会认识到,既unionPtr->array1[i]
和unionPtr->array2[j]
访问*unionPtr
(因为通过数组下标[]
似乎是从数组到指针的衰减区别对待),但不会承认*(unionPtr->array1+i)
和*(unionPtr->array2+j)
也这样做。
附录 - 标准参考:
特定
union foo {int x;} foo,bar;
void test(void)
{
foo=bar; // 1
foo.x = 2; // 2
bar=foo; // 3
}
该标准将之形容为foo.x
的int
类型。如果第二个声明没有访问foo
的存储值,则第三个语句没有任何作用。因此,使用union foo
类型int
的左值的第二条语句访问类型的对象的存储值。纵观N1570 6.5p7:
(脚注88):对象应具有其存储的值由具有以下类型中的一个的左值表达式仅访问
脚注88)该列表的目的是,以指定的对象可以或可以不被混叠的那些情况。
注意,不存在上面给出union foo
使用类型int
的左值访问类型的对象的权限。由于上述是一种约束,任何违法行为调用UB即使构建体的行为否则将由标准来定义。
不,它不是形式上正确的。
在C语言中,你可以做什么的,它可以工作,但像这样的结构是炸弹。今后的任何修改可能导致大失败。
工会保留的内存空间来保存最大的IT元素:
工会的大小足以容纳最大的成员。
在背面的空间是不够的。
考虑:
union
{
char a;
int b;
double c;
} myunion;
char c;
((union myunion *)&c)->b = 0;
将创建一个内存破坏。
标准定义的含义:
成员的至多一个的值可以被存储在任何时间联合对象。一个指向联合对象,适当地转换,指向每个成员(或如果一个构件是一个位字段,然后到单元在其中驻留),并且反之亦然。
强制规定每个工会成员开始在工会的起始地址点,并明确指出,编译器应就它的每个元素的适当边界对齐工会,这意味着要选择正确的每个成员的对齐。由于标准的路线通常是2的幂,作为经验法则的联盟将获得符合要求的最大对齐元素的边界上对齐。