11.4 共享内存
前面讨论了如何使用位字段节省内存,这一般应用于逻辑变量。C语言还有另一个功能,可以将几个变量放在相同的内存区。这个功能在内存短缺时比位字段应用得更广,因为实际上,我们常常使用几个变量,但其中只有一个变量在任意给定的时刻都有有效值。
多个变量共享内存的另一种情形是,程序处理许多不同类型的数据,但是一次只能处理一种,要处理的类型在执行期间确定。第三种可能是,要在不同的时间访问相同的数据,但在不同的情况下该数据的类型是不同的。例如对于一组数值类型的变量,要把它们当成char类型的数组,以便能将它们作为一块数据来移动。
11.4.1 联合
在C语言中允许在多个不同变量共享同一内存区的功能称为联合(union)。声明联合的语法类似于结构,给联合指定标记名称的方式通常也是类似的。定义联合要使用关键字union。例如下面的语句声明一个联合被三个变量共享。
union u_example
{
float decval;
int *pnum;
double my_value;
}U1;
上述语句用标记符名称u_example声明一个联合,它由浮点值decval、整数指针pnum和双精度浮点变量my_value共享。该语句定义了一个联合的实例,即变量U1。也可以用下面的语句声明这个联合的其他实例:
union u-example U2, U3;
联合成员的访问方式和结构成员完全相同。例如,要指定U1和U2成员的值,可以编写:
U1.decval = 2.5;
U2.decval = 3.5 * U1.decval;
试试看:使用联合
下面是一个使用联合的例子:
/* Program 11.8 The operation of a union */
#include <stdio.h>
int main(void)
{
union u_example
{
float decval;
int pnum;
double my_value;
} U1;
U1.my_value = 125.5;
U1.pnum = 10;
U1.decval = 1000.5f;
printf("\ndecval = %f pnum = %d my_value = %lf",
U1.decval, U1.pnum, U1.my_value );
printf("\nU1 size = %d\ndecval size = %d pnum size = %d my_value"
" size = %d",sizeof U1, sizeof U1.decval,
sizeof U1.pnum, sizeof U1.my_value);
return 0;
}
代码的说明
这个例子示范了联合的结构和基本操作。U1联合的声明如下:
union u_example
{
float decval;
int pnum;
double my_value;
}U1;
联合的这三个成员的类型是不同的,它们需要的存储空间也不同(假设编译器给int类型指定两个字节)。
在赋值语句中,给联合实例U1的每个成员赋值如下:
U1.my_value = 125.5;
U1.pnum = 10;
U1.decval = 1000.5f;
注意,引用联合成员的方式和引用结构成员的方法相同。
下面的两行语句输出这三个成员的值,联合U1占用的字节数及每个成员占用的字节数。输出如下所示(如果机器给int类型的变量指定4个字节,输出就与此类似):
decval = 1000.500000 pnum = 8192 my_value = 125.50016
U1 slze = 8
decval slze = 4 pnum size = 2 my_value size = 8
首先要注意,只有最后一个变量的值是正确的,其他两个都是错的。这在意料之中,因为它们共享同一块内存空间。其次,my_value成员没有被毁坏。这是因为只修改了my_value中最不重要的部分。正常情况下,这么小的错误很容易被忽略掉,但最后的结果可能很糟。在使用联合时,要特别注意不要使用无效的数据。
注意:
从输出可知,联合所占的字节数是其最大的成员所占的空间。
11.4.2 联合指针
也可以用下列语句定义联合指针:
union u_example *pU;
有了指针后,就可以修改联合的成员了。如下列语句:
pU = &U2;
U1.decval = pU->decval;
第二行赋值语句中,等号右边的表达式pU–>decval等于U2.decval。
11.4.3 联合的初始化
声明联合时,若需要初始化联合的实例,只能用和联合中第一个变量相同类型的常量初始化。以u_example为例,只能用float常量去初始化,如下所示:
union u_example U4 = 3.14f;
可以重新安排联合中成员的顺序,将要初始化的成员作为第一个成员。联合中成员的顺序并不重要,因为它们都重叠在同一个内存区中。
11.4.4 联合中的结构成员
结构和数组可以是联合的成员。反之,联合也可以是结构的成员。例如:
struct my_structure
{
int numl;
float num2;
union
{
int *pnum;
float *pfnum;
} my_U;
} samples[5];
这里声明了一个结构类型my_structure,它包含一个没有标记符名称的联合,所以这个联合的实例只能存在于结构的实例之中。这种联合常常称为匿名联合。上面的语句还定义了一个包含5个结构实例的数组samples。结构中的联合由两个指针共享。要使用联合成员,其表示法和嵌套的结构相同,例如,要访问结构数组中第三个元素的int指针,要使用如下语句中左边的表达式:
samples[2].my_U.pnum = &my_num;
首先,假设变量my_num已声明为int类型。使用存储在联合中的值时,总是会提取上次赋予联合的值。这似乎很明显,但实际上,很容易将最近存储为整数的值当成float,有时错误很微小,例如程序11.7输出的my_value值。自然,这样通常会得到垃圾值。常常采用的一种方法是将联合嵌入结构中,该联合也有一个成员,指定在联合中当前存储的值的类型。例如:
/* Type code for data in union */
#define TYPE_LONG 1
#define TYPE_FLOAT 2
#define TYPE_CHAR 3
struct Item
{
int u_type;
union
{
long integer;
float floating;
char ch;
} u;
} var;
这段代码定义了Item结构类型,它包含两个成员:int类型的值u_type和匿名联合的一个实例u。这个联合可以存储long类型、float类型或char类型的值,u_type成员用于记录当前存储在u中的类型。
为var设置一个值:
var.u.floating = 2.5f;
var.u_type = TYPE_FLOAT;
在处理var时,需要检查它存储了什么类型的值。下面的例子说明了具体的做法:
switch(var.u_type)
{
case TYPE_FLOAT:
printf("\nValue of var is %10f", var.u.floating);
break;
case TYPE_LONG:
printf("\nValue of var is %10ld", var.u.integer);
break;
case TYPE_CHAR:
printf("\nValue of var is %10c", var.u.ch);
break;
default:
printf("\nInvalid union type code.");
break;
}
使用联合,主要是为了便于把这样的代码放在函数中。





