Skip to content

LV100-共用体

一、共用体简介

1. 什么是共用体

在 C 语言中,不同数据类型的数据可以 使用共同的存储区域,这种数据构造类型称为共用体,简称 共用,又称 联合体。共用体在定义、说明和使用形式上与结构体相似。两者本质上的不同仅在于使用内存的方式上

2. 怎么定义?

定义一个共用体数据类型,我们需要用到 union 关键字:

c
union union_name
{
    data_type member_name1;
    data_type member_name2;
    ...
    data_type member_nameN;
};

各部分说明如下:

union 共用体的关键字,表明这是一个共用体的定义
union_name 共用体的名称,与普通变量名要求一样
data_type 共用体内成员的数据类型,可以是基本变量类型,指针类型,枚举类型,联合体类型或者结构体类型等。比如,char,int 等
member_nameN 共用体内成员的名称(成员变量名),命名规则与变量相同

(1)共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员。

(2)共用体 占用的内存等于最长的成员占用的内存

(3)共用体使用了内存覆盖技术, 同一时刻只能保存一个成员的值,如果对新的成员赋值,就会把原来成员的值覆盖掉。

二、共用体的长度

1. 怎么计算?

我们可以使用 sizeof 函数来求得所定义共用体的长度。共用体既然共用同一段内存区域,那么它的长度就应该 等于共用体成员中最长的数据类型的长度

2. 使用实例

c
#include <stdio.h>

/* 定义共用体数据类型 */
union data1
{
    char a;
    short b;
    int c;
};

union data2
{
    char a;
    short b;
};

int main(int argc, char *argv[])
{ 
    union data1 temp1; /* 定义共用体变量 */
    union data2 temp2; /* 定义共用体变量 */

    printf("sizeof(temp1) = %ld\n", sizeof(temp1));
    printf("sizeof(union data1) = %ld\n\n", sizeof(union data1));

    printf("sizeof(temp2) = %ld\n", sizeof(temp2));
    printf("sizeof(union data2) = %ld\n", sizeof(union data2));
    return 0;
}

在终端执行以下命令:

shell
gcc test.c -Wall # 编译程序 
./a.out          # 执行可执行文件

会看到有如下信息输出:

shell
sizeof(temp1) = 4
sizeof(union data1) = 4

sizeof(temp2) = 2
sizeof(union data2) = 2

我们会发现(我用的 linux 是 64 位的),总的大小是等于最长的数据所占空间大小的。

三、在内存中的存储

既然共用体的成员共用一段内存空间,那打印地址的话,他们怎么分布呢?

c
#include <stdio.h>

/* 定义共用体数据类型 */
union data
{
    char a;
    short b;
    int c;
};

int main(int argc, char *argv[])
{ 
    union data temp; /* 定义共用体变量 */
    
    printf("&temp   = %p\n", &temp);
    printf("&temp.a = %p\n", &temp.a);
    printf("&temp.b = %p\n", &temp.b);
    printf("&temp.c = %p\n", &temp.c);
    return 0;
}

在终端执行以下命令:

shell
gcc test.c -Wall # 编译程序 
./a.out          # 执行可执行文件

会看到有如下信息输出:

shell
sizeof(temp) = 4
sizeof(union data) = 4
&temp   = 0x7ffc7c3d2b14
&temp.a = 0x7ffc7c3d2b14
&temp.b = 0x7ffc7c3d2b14
&temp.c = 0x7ffc7c3d2b14

从例子中不难看出,这几个成员的地址都是一样的,他们确实共用同一段内存。

四、怎么赋值?

看到这里,我会想,它跟结构体用法一样,但是却又共用同一段内存,那我还能给它赋值嘛?接下来我们就开看一看成员赋值时的变化情况。

1. 测试示例

c
#include <stdio.h>

/* 定义共用体数据类型 */
union data
{
    char a;
    short b;
    int c;
};

int main(int argc, char *argv[])
{ 
    union data temp; /* 定义共用体变量 */
    printf("sizeof(temp) = %ld\n", sizeof(temp));
    printf("sizeof(union data) = %ld\n\n", sizeof(union data));
    printf("&temp.a = %p,&temp.b = %p,&temp.b = %p\n", &temp.a, &temp.b, &temp.c);
    printf("temp.a\t\ttemp.b\t\ttemp.c\n");
    printf("%c(%#x)\t%#hx\t\t%d(%#x)\n", temp.a, temp.a, temp.b, temp.c, temp.c);
    temp.c = 0x40;
    printf("%c(%#x)\t\t%#hx\t\t%d(%#x)\n", temp.a, temp.a, temp.b, temp.c, temp.c);
    temp.a = '9';
    printf("%c(%#x)\t\t%#hx\t\t%d(%#x)\n", temp.a, temp.a, temp.b, temp.c, temp.c);
    temp.b = 0x2059;
    printf("%c(%#x)\t\t%#hx\t\t%d(%#x)\n", temp.a, temp.a, temp.b, temp.c, temp.c);
    temp.c = 0x3e25ad54;
    printf("%c(%#x)\t\t%#hx\t\t%d(%#x)\n", temp.a, temp.a, temp.b, temp.c, temp.c);

    return 0;
}

在终端执行以下命令:

shell
gcc test.c -Wall # 编译程序 
./a.out          # 执行可执行文件

会看到有如下信息输出:

shell
sizeof(temp) = 4
sizeof(union data) = 4

&temp.a = 0x7ffc18626194,&temp.b = 0x7ffc18626194,&temp.b = 0x7ffc18626194
temp.a          temp.b          temp.c
�(0xfffffffc)   0x7ffc          32764(0x7ffc)
@(0x40)         0x40            64(0x40)
9(0x39)         0x39            57(0x39)
Y(0x59)         0x2059          8281(0x2059)
T(0x54)         0xad54          1042656596(0x3e25ad54)

为什么要先对整型成员赋值呢?这是因为整型成员赋值后可以将这一个字节都给初始化掉,未初始化的时候谁也不知道里边是什么,这也是为了保证结果的准确性。接下来我们来分析一下数据的变化情况。

2. 结果分析

c
union data
{
    char a;
    short b;
    int c;
};

【说明】红色字体为每次变化的位置。

  • temp.c = 0x40; 赋值后
0x7ffc18626194 0x7ffc18626195 0x7ffc18626196 0x7ffc18626197
0x40 0x00 0x00 0x00
temp.a = 0x40
temp.b = 0x40
temp.c = 0x40
  • temp.a = '9'; 赋值后
0x7ffc18626194 0x7ffc18626195 0x7ffc18626196 0x7ffc18626197
0x39 0x00 0x00 0x00
temp.a = '9' (ASCII 为 0x39)
temp.b = 0x39
temp.c = 0x39
  • temp.b = 0x2059; 赋值后
0x7ffc18626194 0x7ffc18626195 0x7ffc18626196 0x7ffc18626197
0x59 0x20 0x00 0x00
temp.a = 0x59
temp.b = 0x2059
temp.c = 0x39
  • temp.c = 0x3e25ad54; 赋值后
0x7ffc18626194 0x7ffc18626195 0x7ffc18626196 0x7ffc18626197
0x54 0xad 0x25 0x3e
temp.a = 0x54
temp.b = 0xad54
temp.c = 0x3e25ad54

成员 a 、 b 、 c 在内存中“对齐”到一头,对 a 赋值修改的是前一个字节,对 b 赋值修改的是前两个字节,对 c 赋值修改的是全部字节。也就是说, a 、 b 会影响到 c 的一部分数据,而 c 会影响到 a 、 b 的全部数据。

根据 temp.a 、 temp.b 和 temp.c 的分析,可以得出数据在内存中的存储形式。按照最后一次整型的赋值,我们再通过根据 temp.a 、 temp.b 的值,就可以分析出这四个字节的数据的存储是符合小端模式的,高字节存储在高地址,低字节存储在低位,同时这也是一种 判定大小端 的方式(大小端可以看后边的关于大小端的笔记)。