文章链接:https://codemouse.online/archives/2020-03-12223953
结构体内存对齐
- 元素是按照定义顺序一个一个放到内存中去的,但并不是紧密排列的。从结构体存储的首地址开始,每个元素放置到内存中时,它都会认为内存是按照自己的大小来划分的,因此元素放置的位置一定会在自己宽度的整数倍上开始。
使用场景
- 通常在一个结构体中,嵌套了别的结构体的时候。
例如:在linux中默认4字节对齐,如果结构体中,第一个结构体变量14字节,第二个20字节,第三个8字节,那么第一和第二个之间就会出现两个字节的空挡,直接内存dump的时候就会出问题,而且问题很隐蔽。答案就不是42了,是(14+2+20+8=44) - 做协议的时候。
- 要做内存dump的时候。
内存对齐的原因
-
平台问题(移植问题):不是所有的硬件平台都能访问任意地址上的任意数据的,某些硬件平台只能在某些地质处取某些特定类型的数据,否则跑出硬件异常。
-
性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要做两次内存访问,而对齐的内存只需要访问一次。
总结:拿空间换时间
获取结构体中某一元素的位置
使用offsetof宏来判断结构体中成员的偏移地址,在stddef.h中定义
#define offsetof(type,menber) (size_t)&(((type*)0)->member)
// 由于从0处算,那么获取到的结构体元素地址也就是他所在结构体的偏移值
设置默认对齐数
#pragma pack(value) //value只能是1,2,4,8,16
#pragma pack() //设置为默认对齐数
结构体内存对齐规则
- 起始情况:第一个成员在与结构体变量偏移量为0的地址处。
- 中间情况:成员变量的位置必定是编译器默认的一个对齐数 与 该成员大小的较小值的整数倍。
- 结尾情况:结构体的总大小为结构体最宽基本类型成员大小的整数倍。
- 默认情况:VS中默认对齐数的值为8, Linux中的默认值为4。
举例
环境:32位机
默认char一字节,short二字节,int,float四字节,double八字节
struct A{
char a;
int b;
float c;
double d;
int e;
};
解析:
对齐数为4:
a为首个成员,从0出开始,
b大于等于对齐数,要在新的内存单位开始,也就是4处,
cde同理.大小为24刚好为默认对齐数的整数倍,所以最后大小为24.
对齐数为8:
a为首个成员,从0出开始,
b小于默认对齐数,自身大小作为对齐数,则存放在4处,
c同理,则存放在8处,
d大小等于对齐数,存放在8的整数倍上,所以移动到了16处存放,
e小于对齐数,自身大小作为对齐数,则存放在16+8=24处,那么总大小为28,
但由于结构体大小必须为默认对齐数的整数倍,所以总大小为32.
添加上结构体嵌套,这次默认对齐数为8
struct A{
char a;
int b;
float c;
double d;
int e;
};
struct B{
char a;
short b;
struct A c;
struct A d[2];
struct A* e;
};
- 解析:
a为首个成员,从0出开始,
b小于默认对齐数,自身大小作为对齐数,则存放在2处,
c大小大于对齐数,存放在对齐数的整数倍上,所以移动到了8处存放,
d与c同理,则存放在8+32=40处,
e存放在40+2*32=104处,
总大小为108,
但由于结构体大小必须为默认对齐数的整数倍,所以总大小为112.
验证程序
#pragma pack(8)
#include <stdio.h>
#include <stddef.h>
struct A{
char a;
int b;
float c;
double d;
int e;
};
struct B{
char a;
short b;
struct A c;
struct A d[2];
struct A* e;
};
void main()
{
struct B a = { 0 };
printf("%d \n", offsetof(struct B, a));
printf("%d \n", offsetof(struct B, b));
printf("%d \n", offsetof(struct B, c));
printf("%d \n", offsetof(struct B, d));
printf("%d \n", offsetof(struct B, e));
printf("sum=%d \n",sizeof(a));
}