1.1 共用体 Union
结构体占用的内存大于等于所有成员占用的内存的总和(成员之间可能会存在缝隙);
共用体占用的内存等于最长的成员占用的内存。
共用体使用了内存覆盖技术,同一时刻只能保存一个成员的值,所有的数据成员具有相同的起始地址,如果对新的成员赋值,就会把原来成员的值覆盖掉。
参见 linux spin_lock 结构体实现
linux/arch/arm64/include/asm/spinlock_types.h
typedef struct {
union {
u32 slock;
struct __raw_tickets {
#ifdef __ARMEB__
u16 next;
u16 owner;
#else
u16 owner;
u16 next;
#endif
} tickets;
};
} arch_spinlock_t;
1.1.1 位域结构
位域出现的原因是由于某些信息的存储表示只需要几个 bit
位就可以表示而不需要一个完整的字节,同时也是为了节省存储空间和方便处理:
struct 位域结构名
{
类型说明符 位域名:位域长度
}
例子:
struct bit_struct
{
int bit1:3;
int bit2:5;
int bit3:7;
} data;
其中 bit_struct
表示位域结构体,bit1、bit2、bit3 表示对应的位域,data 表示位域结构体定义的变量。整个位域结构体占用 2 个字节:
- bit1 占 3 位;
- bit2 占 5 位,bit1 和 bit2 共用一个字节;
- bit3 占 7 位,独占一个字节。
说明:
- 位域必须存储在同一个类型中,不能跨类型,同时也说明位域的长度不会超过所定义类型的长度。如果一个定义类型单元里所剩空间无法存放下一个域,则下一个域应该从下一单元开始存放。
例如:在 ARM32 架构下, 一个int
类型占用 32 bits,假设用掉了 25 bits,还剩7 bits,这时要存储一个 8 bits 的位域元素,那么这个元素就只能从下一个 int 类型的单元开始而不会在前面一个 int 类型中占7为后面的 int 类型中占 1 位。 - 如果位域的位域长度为 0 表示是个空域,同时下一个域应当从下一个字节单元开始存放。
- 使用无名的位域来作为填充和调整位置,切记该位域是不能被使用的。
- 位域的本质上就是一种结构体类型,不同的是其成员是按二进制位来分配的。
1.1.2 共用体中嵌套结构体
案例一 (机器为小端模式):
#include <stdio.h>
typedef union
{
unsigned int u;
struct
{
unsigned char a :1;
unsigned char b :1;
unsigned char c :6;
unsigned char d :1;
} ST;
} UN_t;
static int main(void)
{
UN_t un;
un.u = 0;
un.ST.a = 1;
un.ST.b = 2;
un.ST.c = 3;
un.ST.d = 4;
printf("%d\n", un.u);
return 0;
}
1)注意联合体的定义,就是组成联合体的变量共用一个空间, 这个 例子中变量 u
和 ST
共用一个空间;
3)基于小端结构, 数据的低字节保存在内存的低地址中, ST
占用 9 bits
, 与变量 u(32Bit)
共用低位的 9
位。
4)根据小段结构,变量 a
的地址应该最低,往后依次是 b, c, d;
5)un.u = 0
; 执行这一步,变量对应空间二进制全部为 0x0
,即 0b00000000 00000000 00000000 00000000
;
6)un.ST.a = 1
; 执行这一步,变量最后一位变化,即 0b00000000 00000000 00000000 00000001
;
7)un.ST.b = 2;
执行这一步,由于1 bit
空间无法存储 2
,所以赋值被截断,原值不变;
8)un.ST.c = 3
; 执行这一步,变量第 3-8 bit
发生变化,变量值变为 0b00000000 00000000 00000000 00001101
;
9)un.ST.d = 4
; 执行这一步,由于 1 位空间无法存储 4,所以赋值被截断,原值不变;
10)所以最终的结果就是: 0b00000000 00000000 00000000 00001101
;
11)printf("%d\n", un.u)
; 输出结果就是13
。
案例二 I2C 驱动中对位域的使用:
unsigned
也是占用 32bit
空间。
typedef union {
unsigned value; //bit[31-0]
struct {
unsigned data : 8; /* Data:占用第8bit bit7-0 */
unsigned icr_start : 1; /* ICR Start 占用第8bit
unsigned icr_stop : 1; /* ICR Stop 占用第9bit
unsigned icr_acknak : 1; /* ICR ACKNAK 占用第10bit
unsigned icr_tb : 1; /* ICR Transfer Byte 占用第11bit
unsigned reserved : 20; /* Unused bit[12-31]
} s;
} fifo_entry_t;
s
和 value
共用 32bit
。
前 8 bit (0-7bit) 由于发送 data 数据;
第 8 bit 用于控制 I2C Controller 的 start 信号;
第 9 bit 用于控制 I2C Controller 的 stop 信号;
第 10 bit 用于控制 I2C Controller 的 ACK信号;
第 11 bit 用于控制 I2C Controller 启动发送;
第 12-31 bits 没有使用。
需要注意的是共用体变量不能初始化:
下面错误做法:
union data
{
int i;
char ch;
float f;
} a = { 1, 'a', 1.5 } ;