普通嵌套
/* 小端存储(低字节位——低地址位), 交叉存放 */
union Myunion
{
int a;
struct
{
char ch1;
char ch2;
char ch3;
char ch4;
};
};
// 16909320
// 0001,00000010,00000100,00001000
// 1 2 4 8
int main()
{
Myunion u;
int n = 16909320;
u.a = n;
printf("%d %d %d %d \n", u.ch1, u.ch2, u.ch3, u.ch4);
// 8 4 2 1
return 0;
}
嵌套 + 位域(位段)
union Myunion_bit
{
int a;
struct
{
unsigned char ch1 : 4;
unsigned char ch2 : 4;
unsigned char ch3 : 4;
unsigned char ch4 : 4;
unsigned char ch5 : 4;
unsigned char ch6 : 4;
unsigned char ch7 : 4;
unsigned char ch8 : 4;
};
};
// 16909320
// 0001, 0000 0010, 0000 0100, 0000 1000
// 1 0 2 0 4 0 8
int main()
{
Myunion_bit u;
int n = 16909320;
u.a = n;
printf("%d %d %d %d %d %d %d %d \n", u.ch1, u.ch2, u.ch3, u.ch4, u.ch5, u.ch6, u.ch7, u.ch8);
// -8 0 4 0 2 0 1 0 当 ch 有符号
// 8 0 4 0 2 0 1 0 当 ch 无符号
return 0;
}
注:
在结构体中,如果类型为 char 类型。即有符号类型时,输出的u.ch1
为 -8
。
分析:
- 按照位域的特点,ch1应该分配了4 bit的二进制,
1000
。 - 而
-8
的二进制是1111 1000
。 - 推测,在使用 u.ch1 时会将不完整的数据右移至完整。(右移符号位填充最高位数据)
检验代码:
bitset<8>bit = u.ch1;
cout << bit << endl; // 输出二进制
bit = u.ch3;
cout << bit << endl; // 输出二进制
输出结果:
11111000 // u.ch1 = -8
00000100 // u.ch3 = 4
嵌套 + 位域——关于内存对齐
在结构体划分位段时,会受到自身变量类型的影响。如果该类型剩余空间不足以继续分配位段,则会自动对齐至下一个内存单元中,进行分配。
错误代码:
union MyUnion
{
unsigned short num; // 16 位
struct
{
unsigned char al : 6; // 低 6位
unsigned char ah : 6; // 中 6位
unsigned char hh : 4; // 高 6位
};
};
int main()
{
MyUnion un;
un.num = 0x111; // 0000 0001 0001 0001
int n = un.hh; //error: 使用了未初始化的局部变量“un”
return 0;
}
在上述结构中,匿名struct内部使用了 char 类型的变量进行位段的划分,而 char 类型是以“一字节,八比特”进行内存对齐。因此我们在使用 struct 内第三个变量时产生错误。
过程分析:
- 我们期望的是,将 num 分为三部分存取到 al、ah、hh中。如下所示:
unsigned short num = 0000 0001 0001 0001
unsigned char al : 6; // 低 6位
= 0000 0001 0001 0001
unsigned char ah : 6; // 中 6位
= 0000 0001 0001 0001
unsigned char hh : 4; // 高 6位
= 0000 0001 0001 0001
- 实际却是 ah 在划分位段时,由于 char类型 1字节内存对齐,在num的低字节中仅存 2 位比特位,不够 ah 的6位域,进而向后内存对齐,使用 num 的高字节进行位段划分。所以 al 与 ah 对应获取的位段如下:
unsigned char al : 6; // 低 6位
= 0000 0001 0001 0001
unsigned char ah : 6; // 中 6位
= 0000 0001 0001 0001
因此, hh 就没有了可划分的位段。
解决方案:
将unsigned char
类型换成 unsigned short
,使之在内存对齐时,使用2字节对齐。
union MyUnion
{
unsigned short num; // 16 位
struct
{
unsigned short al : 6; // 低 6位
unsigned short ah : 6; // 中 6位
unsigned short hh : 4; // 高 6位
};
};
此时 num、al、ah、hh 的值分别为:
un.num = 0x0111; // 0000 0001 0001 0001
al = 0x0010 // 低 6位 0b00,01 0001
ah = 0x0004 // 中 6位 0b00,00 0100
hh = 0x0000 // 高 6位 0b00,00 0000
本质上,所有数据都通过二进制存储的内存或磁盘上,通过以上位域(位段)的学习,我们可以将char类型做到更小以便于达到一个比特,这样我们就可以直接从内存中提取到数据以二进制在内存中存储的最原始的状态。
因此,我们可以利用这一特点实现直接获取一个数据(这里主要指整数)的二进制表示:
数字转二进制(4种方法) | 位域(位段)应用 —— 从内存中提取数字的二进制