在数据结构 -- 结构体Struct一文中详细介绍了结构体的定义以及内存对齐。在C语言中,还有另外一种和结构体非常类似的语法,叫做共用体Union),也称为联合体。它的定义格式为:

union 共用体名{
    成员列表
};

1. 定义共用体变量

和结构体一样,共用体也是一种自定义的数据类型,是创建变量的模板,不占用内存空间。共用体变量才包含了实实在在的数据,需要内存空间来存储。共用体可以通过下面两种方式来定义:

  • 方式一:先定义共用体,再定义共用体变量 
//定义data共用体
union data{
    int n;
    char ch;
    double f;
};

//定义两个共用体变量
union data a, b;
  • data为共用体名,里面包含n、ch、f这3个成员。ab则为两个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为例,分别对其成员赋值,内存空间的变化情况就如下图所示:

unlua 如何处理结构体 结构体中union_内存空间

图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的地方存放,各成员内存会存在重,修改一个成员会影响其余所有成员。
  • 结构体占用的内存大于等于所有成员占用的内存的总和(成员之间可能会存在缝隙)。
    共用体占用的内存等于最长的成员占用的内存。共用体使用了内存覆盖技术,同一时刻只能保存一个成员的值,如果对新的成员赋值,就会把原来成员的值覆盖掉。