一天学一点C++之联合体

联合体与结构体类似也是一种多类型数据的结合体,但是它与结构体有一个最大的不同就是二者的内存分配机制。

首先我们来讲讲内存,内存在计算机中是以何种形式存在的。

首先我们应该明确,计算机存储的最小单位 :比特。一个比特代表了0和1两种(不通电和通电)状态中的一个, 即一个比特可以表示两种状态,计算机一般将8个比特组织为一个字节,古一个字节可以表示2的8次方种状态,而给每个字节编一个地址,大家可以通过地址访问到该字节。

大家可以将内存想象为一排连续的柜子,每个柜子即为一个字节,我们为了方便给每个柜子一个编号,这个编号即为地址。

#include<iostream>
using namespace std;
union MyUnion
{
	int data1;
	double data2;
	char s[10];
};
struct MyStruct
{
	int data1;
	double data2;
	char s[10];
};
int main() {
	cout << sizeof(MyUnion) << endl;
	cout << sizeof(MyStruct) << endl;
	return 0;
}

大家可以看到其实结构体和联合体特别相似,定义的方式几乎一模一样。运行结果如下:

(我们可以用sizeof()这个函数来查看该变量在内存中占用的字节数) 

两者所占用的内存大小不一样,但为什么结构体所占用的内存大小是32能?为什么不是int(4字节)+double(8字节)+ s[10](10字节)= 22字节呢?

这里我要补充一个概念,叫做内存对齐,在计算机的内存中,确实是按顺序往后存储,但前一个变量与后一个变量不会总是紧紧挨着,它们之间有时会相隔一段空间,而这其中的规则就是我接下来要讲的内存对齐,我们举个例子来说明:

struct Example {
    char a;       // 1字节,对齐要求为1
    int b;        // 4字节,对齐要求为4
    short c;      // 2字节,对齐要求为2
};

编译器在处理结构体或联合体时,会自动插入填充字节(padding)以确保结构体的每个成员都按照其自然对齐要求对齐。这种填充通常发生在成员变量之间,以及结构体的末尾。编译器的填充行为遵循以下原则:

  1. 结构体首地址对齐:结构体的首地址必须是其最大成员的自然对齐要求的整数倍。

  2. 成员对齐:结构体中的每个成员都会根据其自身的自然对齐要求进行对齐。如果成员的偏移量不是其自然对齐要求的整数倍,编译器会在成员前插入填充字节。

  3. 结构体总大小对齐:结构体的总大小必须是其最大成员的自然对齐要求的整数倍。如果结构体的总大小不是,编译器会在最后添加填充字节。

        变量a位于0地址不需要填充。(在这里0不能算作4的倍数,如果int 位于第一个,那么不能从0开始而是要从4开始,而char自然对齐为1,意味着它放在任何一个位置都不会又影响。)

        b从1地址开始存,但是1地址并不是4的倍数,于是跳到地址编号为4的空间存储,b占4个字节(4, 5, 6, 7)。

        c从地址8开始存储,8刚好是2的倍数于是c置于编号为的8地址处。

最后整个结构体所占用的内存大小必须是4的倍数于是占用12个字节(0~11)

| a | paddding | b | c | padding | padding | padding |
---------------------------------------------------
  0   1        4    6    7    8    9    10

 对于联合体我们也举一个例子来进行说明:

union MyUnion {
    int data1;
    double data2;
    char s[10];
};

先和大家寿说明一下:联合体所有成员共享同一块内存。

在这个联合体中:

  1. data1: int类型,大小为4字节,对齐要求为4字节。
  2. data2: double类型,大小为8字节,对齐要求为8字节。
  3. s: char数组,大小为10字节,但对齐要求为1字节(char的自然对齐)。

联合体的对齐和大小计算如下:

  1. 确定最大对齐要求data2的对齐要求为8字节,这是联合体中最大的对齐要求。

  2. 计算联合体的大小:由于所有成员共享同一块内存,每个成员都是从0开始,先求出最大空间的成员所占的字节数再判断其是否满足最大成员的对齐要求(最大空间与最大:int[10], 与double),如果满足则不变,否则扩充至符合要求出。

  3. 添加填充以满足对齐:由于double的对齐要求,如果联合体的大小为10字节,则不满足data2的对齐要求。因此,编译器需要将联合体的总大小调整到最接近的8字节边界。在10字节之后的最接近的8字节边界是16字节。因此,编译器会在char s[10]之后添加6字节的填充,使得联合体的总大小为16字节,这样data2就可以在8字节对齐的地址上开始。

因此,对于MyUnionsizeof(MyUnion)将返回16字节,即使char s[10]本身只需要10字节。

当然,既然联合体中各个成员共享同一块存储空间,那么自然是会相互影响,修改一个成员可能会影响到另一个成员,比如:

#include<iostream>
using namespace std;

union MyUnion {
    int data1;
    double data2;
    char s[7];
};

int main() {
    MyUnion x;
    x.data1 = 0;
    x.s[0] = 255;
    x.s[1] = 1;
    x.s[2] = 0;
    x.s[3] = 0;
     // 注意:确保字符串长度(包括'\0')不超过数组大小
    cout << sizeof(x) << endl;
    cout << x.data1 << endl;
    return 0;
}

运行结果如下: 

 诶,这是为什么?

在计算机底层都是数据都是用二进制存储的,从s[0]到s[3]组合出来的数即为511,大家可以将s[0]到s[3]的二进制数拼接起来在换算为整数,当然这里还涉及到大小端的问题,大家可以去了解一下。

本片文章就到这里,希望大家多多支持。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值