主要区别
- struct和union都是由多个不同的数据类型成员组成, 但在任何同一时刻, union中只存放了一个被选中的成员, 而struct的所有成员都存在。
- 在struct中,各成员都占有自己的内存空间,它们是同时存在的。一个struct变量的总长度等于所有成员长度之和。
- 在Union中,所有成员不能同时占用它的内存空间,它们不能同时存在。Union变量的长度等于最长的成员的长度。
- 对于union的不同成员赋值, 将会对其它成员重写, 原来成员的值就不存在了, 而对于struct的不同成员赋值是互不影响的。
结构体内存对齐三大原则
- 对于结构体的各个成员,第一个成员的偏移量是0,排列在后面的成员其当前偏移量必须是当前成员类型的整数倍
- 结构体内所有数据成员各自内存对齐后,结构体本身还要进行一次内存对齐,保证整个结构体占用内存大小是结构体内最大数据成员的最小整数倍
- 如程序中有#pragma pack(n)预编译指令,则所有成员对齐以n字节为准(即偏移量是n的整数倍),不再考虑当前类型以及最大结构体内类型
#include<iostream>
using namespace std;
struct A{
char a;
int b;
short c;
};
struct B{
short c;
char a;
int b;
};
int main(){
cout<<sizeof(A)<<endl;
cout<<sizeof(B)<<endl;
return 0;
}
上面两个结构体A和B成员变量类型相同,但是占用的内存空间大小(单位:字节)却不一样
sizeof(A) = 12
sizeof(B) = 8
以上面结构体A为例,第一个成员a是char类型,占用1个字节空间,偏移量为0,第二个成员b是int类型,占用4个字节空间,按照规则1,b的偏移量必须是int类型的整数倍,所以编译器会在a变量后面插入3字节缓冲区,保证此时b的偏移量(4字节)是b类型的整数倍(当前恰好是1倍),第3个成员c为short类型,此时c的偏移量正好是4+4=8个字节,已经是short类型的整数倍,故b与c之间不用填充缓冲字节。但这时,结构体A的大小为8+2=10个字节,按照规则2,结构体A大小必须是其最大成员类型int的整数倍,所以在10个字节的基础上再填充2个字节,保证最后结构体大小为12,以符合规则2.
数据成员 | 前面偏移量 | 成员自身占用 |
---|---|---|
(char) a | 0 | 1 |
缓冲补齐 | 1 | 3(规则1) |
(int) b | 4 | 4 |
(short) c | 8 | 2 |
缓冲补齐 | 10 | 2(规则2) |
类似的,结构体B成员的分析如下:
数据成员- | -前面偏移量- | -成员自身占用 |
---|---|---|
short c | 0 | 2 |
char a | 2 | 1 |
缓冲补齐 | 3 | 1(规则1) |
int b | 4 | 4 |
另一个更复杂的例子:
struct BU {
int number; //4字节
union UBffer {
char buffer[13]; //填充3字节,该成员占16字节空间
int number;
}ubuf;
int aa; //占4字节空间,当前偏移量已补齐为20
double dou; //占8字节空间
}bu;
sizeof(BU) = 4 + 13 + 3(补齐) + 4 + 8 = 32,分析方法类似,在计算aa的偏移量时,我们可以肯定的是一定是int类型的整数倍,由于不作任何缓冲补齐的情况下,number + buffer = 17字节,为了符合规则1,需要填充3个字节。
结构体BU稍微变换下aa和dou成员顺序,则结果就大不相同:
struct BC {
int number; //4字节
union UBffer {
char buffer[13]; //填充7字节,该成员占20字节空间
int number;
}ubuf;
double dou; //占8字节空间,当前偏移量已补齐为24
int aa; //占4字节空间,当前占用空间36字节,最大double类型,还需要根据规则2补齐
}bu;
此时sizeof(BC) = 4 + 13 + 7(规则1补齐) + 8 + 4 + 4(规则2补齐) = 40 (8的整数倍)
我们可能对于结构体类包含union类型成员抱有疑虑,再考虑下面实例:
struct BD {
short number;
union UBffer
{
char buffer[13];
int number;
}ubuf;
}bc;
运行结果是sizeof(BD) = 2 + 2 + 13 +3 = 20,可能你会问,为什么不是2+13+1 = 16,这是因为union类型比较特殊,计算union成员的偏移量时,需要根据union内部最大成员类型来进行缓冲补齐,所以为了保证偏移量为union最大成员int类型的整数倍,需要在number(short类型)后面填充2个字节,前面例子中number是int类型,就没有这个必要了。
再比如:
struct BE {
short number;
union UBffer
{
char buffer[13];
double number;
}ubuf;
}bc;
它的运行结果是sizeof(BE) = 2 + 6 + 13 + 3 = 24,number后面为了与double类型进行对齐而补齐了6个字节,最后再按照规则2补齐了3个字节
考虑规则3:举个例子,在#pragma pack(1)时,以1个字节对齐时,属于最简单的情况,结构体大小是所有成员的类型大小的和。所以sizeof(BU) = sizeof(BC) = 29,这时与成员变量顺序不再相关。其他指定的字节对齐也很好分析。一般而言,奇数个字节对齐没有意义,正常情况下,编码人员不关心编译器对内存对齐所作的工作。
PS: C语言中offsetof()函数可用于查看特定的结构体成员在结构体中的偏移量,编程时可以用于验证上面的说法。其实现类似如下:
#define offsetof(type, member) (size_t)&(((type *)0)->member)
原理是,强制将结构体(类型为type)的起始地址置为0,然后输出其成员的地址,该地址的大小就是成员在结构体中的偏移量。