一、结构体的内存对齐
观察下面不同类型的两个结构体的尺寸
typedef struct A
{
int i1;//4
char c1;//1
}A;
typedef struct B
{
char c2;//1
int i2;//4
char c3;//1
}B;
int main()
{
A a = { 0 };
B b = { 0 };
printf("%d\n", sizeof(a));
printf("%d\n", sizeof(b));
return 0;
}
运行结果:
这两个结构体所占的内存大小与预想的不一样,为什么呢?
1.结构体的对齐规则
结构体的内存对齐就是拿空间来换取时间
(1)第一个成员
放在结构体变量在内存中存储位置的0偏移处开始
(2)从第2个成员往后的所有成员
都放在一个对齐数的整数倍的地址处
注: 对齐数 = 编译器默认的一个对齐数与该成员大小二者的较小值(VS环境中默认的对齐数是8;Linux环境没有默认对齐数,对齐数就是成员自身大小)
(3)结构体总大小
为最大对齐数的整数倍
(4)如果嵌套了结构体的情况
嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
2.例子:分析以下结构体的内存
typedef struct A
{
char c;//1
int i;//4
double d;//8
//现存 char 后,int 存到偏移量为 4(对齐数) 的整数倍的地址处
}A;
int main()
{
A a = { 0 };
printf("%d\n", sizeof(a)); //16
return 0;
}
对这个结构体 A
3.修改默认对齐数
预处理指令 #pragma
#pragma pack(n)
就是将默认对齐数改为n,n取(1, 2, 4, 8, 16) 中任意一个值
例如以下修改方式:
typedef struct A
{
char c1;//1
int i;//4
char c2;//1
}A;
#pragma pack(2) //表示将默认对齐数改为2
typedef struct B
{
char c1;//1
int i;//4
char c2;//1
}B;
#pragma pack() //表示取消修改过的默认对齐数
int main()
{
A a = { 0 };
printf("%d\n", sizeof(a));
B b = { 0 };
printf("%d\n", sizeof(b));
return 0;
}
运行结果:结构体A对齐数没变,为8,得到尺寸是12;结构体B对齐数改为2,得到尺寸是8
二、结构体实现位段的能力
1、位段
位段的声明和结构是类似的,有两个不同:
- 位段的成员必须是 int、unsigned int、char 类型
- 位段的成员名后边有一个冒号和一个数字 (冒号后的数字表示成员占几个bit位)
例如:
struct A
{
int a : 2;//成员占2个bit位
int b : 5;//成员占5个bit位
int c : 10;
int d : 30;
}
A就是一个位段类型
2、位段的内存分配
位段的空间上是按照需要以4个字节(int)或1个字节(char)的方式来开辟的
struct A
{
int a : 2;//成员占2个bit位
int b : 5;//成员占5个bit位
int c : 10;
int d : 30;
};
//先开辟4个字节即32个bit位,然后分配,不够就再开辟4字节空间
int main()
{
printf("%d\n", sizeof(struct A));//8
return 0;
}
位段涉及很多不确定因素(下面提到有四个不确定因素),位段是不跨平台的,注意可移植的程序应该避免使用位段
3、位段的跨平台问题
1.int位段被当成有符号还是无符号数是不确定的
2.位段中最大位的数目不能确定(16位机器最大16,32位机器最大32,比如30在16位机器会出问题)
3.位段中的成员在内存中从左向右分配,函数从右向左分配标准尚未定义
4.当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的
4、位段和结构的比较
与结构相比,位段可以达到同样的效果,并且可以很好的节省空间,但是有跨平台的问题存在