C 高阶系列导航
1. 共用体简介
共用体又称联合体,为一种 C 语言的复合数据类型。共用体与结构体类似的是,同为多种数据类型的集合;不同的是,共用体在同一时刻只能表现出其中一种数据类型,而结构体本身包含着多种数据类型。
如何理解“共用体在同一时刻只能表现出其中一种数据类型”这一句话呢?
可以从数据类型的角度解答。数据类型为对某段内存以特定方式进行解析的方法,比如整型就是对 4 字节大小的内存以其特定方式解析的方法。因此,共用体就是能够以其所包含的数据类型来解析某段内存。简单来说,假如共用体含有整型成员和字符型成员后,当定义共用体变量后,可以分别使用整型与字符型来对该变量所对应的内存进行解析,即解析的是同一片内存空间。
2. 共用体的定义
共用体的定义格式为:
union 共用体名
{
共用体成员 1,
共用体成员 2,
...
};
可以使用 typedef 对共用体类型的名称进行简化:
typedef union 共用体名
{
共用体成员 1,
共用体成员 2,
...
} 共用体类型别称;
甚至省略共用体名:
typedef union
{
共用体成员 1,
共用体成员 2,
...
} 共用体类型别称;
在使用时可以省略 union
直接使用共用体类型别称:
共用体类型别称 value;
3. 共用体的使用方法:
对于共用体变量可以直接使用 = {0}
进行初始化。
共用体的最大特点为其所解析的内存大小为共用体所包含的数据类型的最大解析范围。例如:
union test
{
int a;
char b;
};
int main(void)
{
union test unTmp;
printf("sizeof(unTmp) = %ld\n", sizeof(unTmp)); // sizeof(unTmp) = 4
}
当以上共用体 tmp 使用其成员 a 时表示共用体按整型对变量所在的内存进行解析,当使用其成员 b 时表示共用体按字符型对变量所在的内存进行解析。例如:
union test
{
int a;
char b;
};
int main(void)
{
union test unTmp;
unTmp.a = 70;
printf("unTmp.a = %d\n", unTmp.a); // unTmp.a = 70
printf("sizeof(unTmp.a) = %ld\n", sizeof(unTmp.a)); // sizeof(unTmp.a) = 4
printf("unTmp.b = %c\n", unTmp.b); // unTmp.b = F
printf("sizeof(unTmp.b) = %ld\n", sizeof(unTmp.b)); // sizeof(unTmp.b) = 1
return 0;
}
4. 共用体的应用
以下将列举在工程中较常见的共用体应用场景。
4.1 作为不同种类的同类参数使用
在工程中,共用体常嵌套于结构体出现,应用于需要满足对不同种类进行配置同类参数的场景。
假设存在以下场景:该程序为两种不同的设备 Huawei 与 Apple 的同一套程序,现需设计一个接口,该接口将根据当前设备的类型来配置同类参数。其中,GetDiviceType()
为获取本设备类型的 API。
- 示例:
enum InterfaceType
{
GPIO,
UART,
};
enum DeviceType
{
HUAWEI,
APPLE,
};
typedef struct
{
unsigned int type;
union
{
struct
{
char deviceid[16];
unsigned int interface;
unsigned int pin_num;
} Huawei;
struct
{
char deviceid[16];
unsigned int interface;
} Apple;
};
} DeviceInfo;
int fun(void)
{
DeviceInfo stDevinfo = {0};
if (GetDeviceType() == HUAWEI)
{
stDevinfo.type = HUAWEI;
strcpy(stDevinfo.Huawei.deviceid, "H011");
stDevinfo.Huawei.interface = GPIO;
stDevinfo.Huawei.pin_num = 10;
}
else if (GetDeviceType() == APPLE)
{
stDevinfo.type = APPLE;
strcpy(stDevinfo.Apple.deviceid, "A003");
stDevinfo.Apple.interface = UART;
}
else
{
printf("err device type !! \n");
return -1;
}
return 0;
}
4.2 识别系统的大小端
该场景在实际应用中和面试中都非常常见。
先说明一下大小端:
不同的系统的存储策略因历史原因分为大端存储与小端存储两种模式。大端为高字节存储在低地址,低字节存储在高地址;小端为高字节存储在高地址,低字节存储在低地址。在实际中,由于网络字节序列都是大端模式,因此对于小端系统必须进行大小端的数据转换。
可见,软件自识别系统的大小端为非常重要的功能。
使用共用体识别系统的大小端利用的正是共用体成员都使用同一片内存的特点。方法是:定义一个含整型成员与字符型成员的共用体变量,并赋值于其的字符型成员,假设系统为小端,则整型成员的值为字符型成员的值;假设系统为大端,则整型成员的值为 0。
- 示例:
typedef union
{
int iVar;
char cVar;
} Test;
int main(void)
{
Test obj = {0};
obj.iVar = 1;
if (obj.iVar == obj.cVar)
{
printf("the system is little endian!\n");
}
else
{
printf("the system is big endian!\n");
}
return 0;
}
- 判别原理如下:
对于数据类型而言,所对应的内存模型为从低地址向高地址递增。假设以上的共用体变量 obj 的地址为 0x00001000,由于其包含整型成员,则其大小为 4 字节,即共用体变量 obj 所对应的地址段为 0x00001000 ~ 0x00001003。
现赋值 obj.iVar 为 1,即此时的 obj.iVar 的值为 0x00000001。
当当前系统为大端时,内存状态为:
地址 | 数值 |
---|---|
0x00001003 | 0x01 |
0x00001002 | 0x00 |
0x00001001 | 0x00 |
0x00001000 | 0x00 |
读取 obj.cVar 时会从低地址开始解析一个字节,则此时 obj.cVar 为 0,即 obj.iVar != obj.cVar。
当当前系统为小端时,内存状态为:
地址 | 数值 |
---|---|
0x00001003 | 0x00 |
0x00001002 | 0x00 |
0x00001001 | 0x00 |
0x00001000 | 0x01 |
读取 obj.cVar 时会从低地址开始解析一个字节,则此时 obj.cVar 为 1,即 obj.iVar = obj.cVar。