细说#Pragma Pack(n)与内存对齐

时间:2022-12-30 23:15:39

数据结构对齐与#pragma pack(n)之间的约束:

关于 #pragma pack(n)与复合类型的数据(常见于结构体、数组等)对其方式约束,流传两种类型的解释:

解释一:

每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的“对齐系数”。
规则:
1. 数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照 #pragma pack(n)指定的对齐系数n 与 这个数据成员自身长度两者,最小的那个进行,即 min(n,sizeof(结构成员));
2. 结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度两者中,比较小的那个进行。

解释二:

n 字节的对齐方式 VC 对结构的存储的特殊处理确实提高 CPU 存储变量的速度,但是有时候也带来 了一些麻烦,我们也可以屏蔽掉变量默认的对齐方式,自己可以设定变量的对齐方式。 VC 中提供了#pragma pack(n)来设定变量以 n 字节对齐方式。n 字节对齐就是说 变量存放的起始地址的偏移量有两种情况:
1. 如果 n 大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式。
2. 如果 n 小于该变量的类型所占用 的字节数,那么偏移量为 n 的倍数,不用满足默认的对齐方式。结构的总大小也有个约束条件,分下面两种情况:如果 n 大于所有成员变量类型所占用的字节数,那么结 构的总大小必须为占用空间最大的变量占用的空间数的倍数; 否则必须为 n 的倍数。
身为嵌入式行业的一员,更多的该关注解释一,解释二看看,知道有这么回事儿就成。


实例化

demo:据说是Intel和微软和本公司同时出现的面试题,曾经也是引起火热的讨论,

#pragma pack(8) 
struct s1{
short a;
long b;
};
struct s2{
char c;
struct s1 d;
long long e;
};
#pragma pack()


1.sizeof(s2) = ?
2.s2的c后面空了几个字节接着是d?
答案:
①sizeof(s2)结果为24.
②s2的c后面空了几个字节接着是d?==>3字节
这里边声明下,题意sizeof(short )== 2,sizeof(long )== 4,sizeof(long long )== 8,
说明:
成员对齐有一个重要的条件,即每个成员分别对齐.即每个成员按自己的方式对齐.
也就是说上面虽然指定了按8字节对齐,但并不是所有的成员都是以8字节对齐.其对齐的规则是,每个成员按其类型的对齐参数(通常是这个类型的大小)和指定对齐参数(这里是8字节)中较小的一个对齐.并且结构的长度必须为所用过的所有对齐参数的整数倍,不够就补空字节.==>这里见上边的解释一
关注下struct s2成员d,类型为struct s1,是个结构体类型;对于结构来说,它的默认对齐方式就是它的所有成员使用的对齐参数中最大的一个,S1中所用成员对齐参数最大==4。所以,成员d就是按4字节对齐。==>这里可以适当套用下解释一的第二条

内存对齐

下边抽象下s1、s2成员在内存中的排布:
S1的内存布局:11**,1111, ==>a与b之间有2字节的数据填充;
S2的内存布局:1***,11**,1111,****11111111 ==>c,d之间存在3个字节的数据填充,


额外实例解释

继续实例化几个案例:

#pragma pack(4)
struct node{
int e;
char f;
short int a;
char b;
};

内存排布:1111,1*,11,1, * * * ,==12字节
char f,与pack(4)指定的4 ,取最小值对齐,so按1字节对齐,前边不需要填充;
short int a与4比较取最小 2字节对齐,so前边的char f;需要填充一个字节;
char b与4,知按1字节对齐,前方不需要short int a不需要填充;
其中,最占地的成员为int e,sizeof(int)==4字节;
接下来是数据结构struct node的整体对齐填充,由解释一种的规则2知,min(sizeof(int),4)==4,就是说该数据结构整体需要4字节对齐,即最后还需要填充3字节,来确保符合规则约束。

#pragma pack(4)

struct node{
char f;
int e;
short int a;
char b;
};

内存排布:1***,1111,11,1,*,==12字节
int e与4比较知,知该成员按4字节对齐,前边的char f需要填充3字节数据;
short int a与4比较,知该成员按2字节对齐,前方的int e不需要填充;
char b与4比较,知该成员按1字节对齐,前边的short int a不需要填充,
最后数据结构整体需要按4字节对齐,需要在尾部继续填充一个数据;

#pragma pack(2)
struct node{
char f;
int e;
short int a;
char b;
};

内存排布:1*,1111,11,1,* ==10字节
int e与2比较知,知该成员按2字节对齐,前边的char f需要填充1字节数据;
short int a与2比较,知该成员按2字节对齐,前方的int e不需要填充;
char b与2比较,知该成员按1字节对齐,前边的short int a不需要填充,
最后数据结构整体需要按2字节对齐,需要在尾部继续填充一个数据;

#pragma pack(1)
struct node{
char f;
int e;
short int a;
char b;
};

内存排布:1,1111,11,1, ==8字节
int e与1比较知,知该成员按1字节对齐,前边的char f需要填充0字节数据;
short int a与1比较,知该成员按1字节对齐,前方的int e不需要填充;
char b与1比较,知该成员按1字节对齐,前边的short int a不需要填充,
最后数据结构整体需要按1字节对齐,需要在尾部继续填充一个数据;

补充+扩展

对于数组,比如:
char a[3];这种,它的对齐方式和分别写3个char是一样的.也就是说它还是按1个字节对齐.
如果写: typedef char Array3[3];
Array3这种类型的对齐方式还是按1个字节对齐,而不是按它的长度. 不论类型是什么,对齐的边界一定是1,2,4,8,16,32,64….中的一个.

以上,有情况及时沟通….