声明(六)
2、联合说明符
联合类型是由一系列存储空间重叠的成员构成的类型。关键词union表示指定的类型是联合类型。C语言中联合说明符具有以下两种语法格式:
union attribute-specifier-sequenceopt identifieropt {member-declaration-list} union attribute-specifier-sequenceopt identifier
其中union是C语言关键词;attribute-specifier-sequence是属性说明符序列;identifier是标识符,也是联合标记;member-declaration-list是成员声明列表。
第一种格式声明的联合类型是完整类型,如果缺省标识符(identifier),声明的联合是匿名联合(anonymous structure)。第二种格式声明的联合类型是完整类型还是不完整类型,与其位置有关;如果声明在对应完整类型声明之前,声明类型是不完整类型;如果声明在对应完整类型声明之后,声明类型是完整类型。
union u1; //这里union u1是不完整类型。 union u1{ int i; double d; }; union u2{ int i; double d; }; union u2; //这里union u2是完整类型。
ISO/IEC 9899:2024标准对联合声明作了一些限制,具体如下:
-- 未声明匿名结构或者匿名联合的联合声明应包含成员声明符列表(member-declarator-list)。
union u1{ //合法。 struct { //匿名结构。 int a; double d; }; }; union u2{ //未定义行为。 };
-- 联合成员类型不能是不完整类型或者函数类型,因此联合不能包含自身类型成员,但可以包含指向自身类型对象的指针。
union u1{ int i; union u1 u11; //非法。 }; union u2{ int i; union u2 *ptr; //合法。 }; union u3{ int i; double d[]; //非法,不完整类型。 };
-- 属性说明符序列不应出现在无成员声明列表的联合说明符中,以下情况除外:
union attribute-specifier-sequence identifier;
属性说明符序列中的属性(如果存在)视为联合的属性。
union [[deprecated]] u; //合法,union u类型具有[[deprecated]]属性。 union u{ //合法,从上次声明继承了[[deprecated]]属性。 int i; double d; }; int f1(union u *); //合法,union u类型具有[[deprecated]]属性。 int f2(union [[deprecated]] u uu); //非法,不符合联合中属性的语法格式。
如果联合说明符中存在成员声明列表,则在编译单元内声明了一种新联合类型。成员声明列表是联合成员的声明序列,如果成员声明列表没有直接或者通过匿名结构(或者联合)包含任何命名成员,其行为是未定义的。在终止成员声明列表的}符号之前,联合类型是不完整类型,之后联合类型是完整类型。
union u{ int i; double d; //这里联合类型是不完整的。 }; //这里联合类型是完整的。
联合成员的类型可以是任何完整对象类型(complete object type),但可变修改类型(variably modified type)除外。
int n; union u{ double d; int (*ptr)[n]; //非法,成员类型是可变修改类型。 };
联合成员类型不能是可变修改类型,因为联合成员名不是普通标识符,联合成员具有独立的命名空间。
类型说明符是无标记联合说明符的联合称为匿名联合(anonymous union)。如果联合成员是匿名结构或者匿名联合,匿名结构或者匿名联合的成员认为是包含匿名结构或者匿名联合的联合的成员;如果匿名结构成员或者匿名联合成员包含的结构或者联合也是匿名结构或者匿名联合,上述规则递归适用。
union { struct { int i; }; struct s{ double d; } ss; } uu; uu.i = 5; //合法。 uu.ss.d = 3.14; //合法。 uu.d = 3.14; //非法。
联合对象中每个非位字段成员以适合其类型的实现定义方式对齐。联合应足以容纳其最大成员。任何时候最多只能一个成员值存储在联合对象中。
union { int i; double d; } uu; uu.i = 5; //现在联合中存储的是uu.i的值。 uu.d = 3.14; //现在联合中存储的是uu.d的值。
指向联合的指针经过适当转换后会指向联合成员(如果联合成员是位字段,将指向其所在单元。);反之亦然。指向联合成员的指针转换成字符类型指针时指向相同字节。
union { int i; double d; } uu; int *p1 = (int *)&uu; //指针p1指向uu.i。 *p1 = 5; //uu.i的值为5。 int *p2 = &uu.i; double *p3 = &uu.d; (unsigned char *)p2 == (unsigned char *)p3; //表达式值为1。
联合末尾可能存在未命名填充。
union { int i; double d; } uu; /* ** 编译环境中uu占8个字节, ** uu.i占4个字节,uu.d占8个字节。 */ uu.i = 5; //这时联合末尾存在未命名填充字节。 uu.d = 3.14; //这时联合末尾不存在未命名填充字节。
联合声明中的属性说明符序列适用于声明的联合类型;联合成员声明中的属性说明符序列适用于声明的联合成员。如果不存在联合成员声明符,属性说明符序列不应出现。属性说明符序列只影响其出现的成员声明或者类型名,而不影响同一类型的其它类型或者声明。
union [[deprecated]] u{ //union u具有deprecated属性。 int i; double d; [[maybe_unused]] char ch; //ch具有maybe_unused属性。 };