在数据结构 -- 结构体Struct一文中详细介绍了结构体的定义以及内存对齐。在C
语言中,还有另外一种和结构体非常类似的语法,叫做共用体(Union
),也称为联合体。它的定义格式为:
union 共用体名{
成员列表
};
1. 定义共用体变量
和结构体一样,共用体也是一种自定义的数据类型,是创建变量的模板,不占用内存空间。共用体变量才包含了实实在在的数据,需要内存空间来存储。共用体可以通过下面两种方式来定义:
- 方式一:先定义共用体,再定义共用体变量
//定义data共用体
union data{
int n;
char ch;
double f;
};
//定义两个共用体变量
union data a, b;
-
data
为共用体名,里面包含n、ch、f这3个成员。a
、b
则为两个data类型的共用体变量。
- 方式二:在定义共用体的同时定义共用体变量
- 直接将变量放在共用体的最后即可。union data{ int n; char ch; double f; } a, b;
union data{
int n;
char ch;
double f;
} a, b;
- 如果只需要 a、b两个变量,后面不需要再使用共用体名定义其他变量,那么在定义时也可以省略共用体名。
2. 成员的获取和赋值
共用体使用了内存覆盖机制,同一时刻只能保存一个成员的值,如果对新的成员赋值,就会把原来成员的值覆盖掉。所以共用体不能整体赋值,只能使用点号.获取单个成员,然后再进行赋值操作。
//注:下面每一次赋值后,n、ch、f三个变量的值均会改变
a.n = 40;
a.ch = '9';
a.f = 135;
3. 共用体的内存分配
共用体变量占用的内存大小等于最长的成员占用的内存大小,各成员都会从offset为0处开始存放,修改其中一个成员会影响其余所有成员。来看下面的实例:
//定义一个data共用体
union data{
int n;
char ch;
short m;
};
int main(){
//创建一个共用体变量a
union data a;
printf("a.size = %d, data.size = %d\n", sizeof(a), sizeof(union data) );
//对成员a赋值
a.n = 0x40;
printf("n = %#X, ch = %c, m = %#hX\n", a.n, a.ch, a.m);
//对成员b赋值
a.ch = '9';
printf("n = %#X, ch = %c, m = %#hX\n", a.n, a.ch, a.m);
//对成员c赋值
a.m = 0x2059;
printf("n = %#X, ch = %c, m = %#hX\n", a.n, a.ch, a.m);
//再对成员a赋值
a.n = 0x3E25AD54;
printf("n = %#X, ch = %c, m = %#hX\n", a.n, a.ch, a.m);
return 0;
}
在上面代码中创建了一个共用体变量a,里面有n、ch、m这三种不同数据类型的成员,分别对齐赋值,打印每一次赋值后的三个成员。
%X
:表示将int类型数据以大写字母的形式输出十六位进制数。%c
:表示输出字符。%hX
:表示将short类型数据以大写字母的形式输出十六位进制数。#
:表示输出时加上前缀,用于区分不同进制的数字。比如:0x16,不加#时输出为16,加上输出为0x16。
打印结果为:
a.size = 4, data.size = 4
n = 0X40, ch = @, m = 0X40
n = 0X39, ch = 9, m = 0X39
n = 0X2059, ch = Y, m = 0X2059
n = 0X3e25ad54, ch = T, m = 0XAD54
从打印结果可知,共用体a的内存等于其最长成员int n
所占的内存大小,为4字节。每次修改共用体中的某个成员,都会影响到其他成员的值。
4. 分析共用体的内存分配
共用体的每个成员都从其内存offset为0的地方存放,根据成员数据类型各占用对应字节大小的内存,每次为成员赋值时,都会覆盖修改所占内存,因此,其他成员的值也跟着变动了。以上面的共存体变量a为例,分别对其成员赋值,内存空间的变化情况就如下图所示:
图1 → 图2 → 图3 → 图4 → 图5
图1:表示共用体a及其成员的内存分配情况:
- 每一格代表一个字节,从上到下表示地址由低到高分布。
- a总共4字节,成员n、m、ch均从offset为0的地方开始存放,分别占用4字节、2字节、1字节的内存大小,内存存在重合部分。
图2:表示当赋值a.n = 0x40
后,共用体a的内存空间变化:
1byte = 8bit,所以1个字节最大值为255,换算成十六进制为0xFF,而0x40小于0xFF,所以只需一个字节保存。存储系统的分布方式大多采用小端模式,即在内存的第一个字节里保存0x40,此时成员n、ch、m的值均为0x40。
存储系统分布方式,以0x12345678为例:
1.大端模式:高位在低地址,低位在高地址。即12在低地址,78在高地址。
2.小端模式:高位在高地址,低位在低地址。即12在高地址,78在低地址。
因此,当赋值a.n = 0x40
后,打印结果为:n = 0X40, ch = @, m = 0X40。(0x40对应的字符为@
)
图3:表示当赋值a.ch = '9'
后,共用体a的内存空间变化:
字符
9
的十六进制ASCII码值为0x39,在赋值后,内存里第一个字节的0x40被覆盖,变为0x39。
因此当赋值a.ch = '9'
后,打印结果为:n = 0X39, ch = 9, m = 0X39。
图4:表示当赋值a.m = 0x2059
后,共用体a的内存空间变化:
由于0x2059小于0xFFFF,所以需要两个字节来保存,在赋值后,内存里第一个字节里的39被覆盖,前两个字节分别保存0x59、0x20。
因此当赋值a.m = 0x2059
后,ch为0x59,对应字符Y
,所以打印结果为:n = 0X2059, ch = Y, m = 0X2059。
图5:表示当赋值a.n = 0x3E25AD54
后,共用体a的内存空间变化:
由于0x3E25AD54小于0xFFFFFFFF,所以需要4字节来保存。在赋值后,内存的4个字节分别保存54、AD、25、3E。
因此当赋值a.n = 0x3E25AD54
后,ch为0x54,对应字符T
,m为0xAD54,所以打印结果为:n = 0X3E25AD54, ch = T, m = 0XAD54。
5. 共用体和结构体的区别
- 结构体的第一个成员会从offset为0的地方开始存放,其它成员按顺序存储,各成员会占用不同的内存,互相之间没有影响;
共用体的所有成员都会从offset为0的地方存放,各成员内存会存在重,修改一个成员会影响其余所有成员。 - 结构体占用的内存大于等于所有成员占用的内存的总和(成员之间可能会存在缝隙)。
共用体占用的内存等于最长的成员占用的内存。共用体使用了内存覆盖技术,同一时刻只能保存一个成员的值,如果对新的成员赋值,就会把原来成员的值覆盖掉。