1. 为什么要对齐?
#pragma pack主要是用在字节对齐方面,为什么要对齐呢?
因为计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变量的时候经常在特定的内存地址访问,这就需要各类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
2. 对齐规则
1. 未指定#pragma pack时,系统默认的对齐模数4字节(32位机,X86系统等)。
2. 指定#pragma pack 对齐模数时,实际取pack 对齐模数和默认的最小值。
3. 结构体里面static变量,因为静态变量的存放位置与结构体实例的存储地址无关,是单独存放在静态数据区的,因此用siezof计算其大小时没有将静态成员所占的空间计算进来。
3. 举例说明
#include <stdio.h>
typedef struct student
{
char a; //默认4个字节对齐,char 是1个字节,以4字节对齐,按4个字节处理
short b; //默认4个字节对齐,short 是2个字节,以4字节对齐,按4个字节处理
int c; //默认4个字节对齐,int 是4个字节,以4字节对齐,按4个字节处理
float d; //默认4个字节对齐,float 是4个字节,以4字节对齐,按4个字节处理
double e; //默认4个字节对齐,double 是8个字节,以4字节对齐,按8个字节处理
long f; //默认4个字节对齐,long 是4个字节,以4字节对齐,按4个字节处理
unsigned char g; //默认4个字节对齐,unsigned char 是1个字节,以4字节对齐,按4个字节处理
unsigned short h; //默认4个字节对齐,unsigned short 是2个字节,以4字节对齐,按4个字节处理
unsigned int i; //默认4个字节对齐,unsigned int 是4个字节,以4字节对齐,按4个字节处理
}STU;
int main()
{
printf("%d.\n", sizeof(STU));
return 0;
}
分析:假如我们不使用字节对齐,那么这个结构体大小应当是1+2+4+4+8+4+1+2+4=28个字节,但是实际打印的结果确实是40个字节。这是因为编译器默认以4个字节对齐,不足4个字节(比如short和char),按4个字节处理。大于等于4个字节的就不用管。(比如double是8个字节,就按8个字节算)
但是在实际工程中,如果我们是读取一块一块的数据,这些数据都是连在一起的,比如bmp图片,前14个字节是文件信息头,紧接着是40个字节的图像信息头。如果我们不用结构体对齐操作的话。那就乱了套了,数据就读取失败。所以我们要在结构体前加上#pragma pack(1),以一个字节对齐,使用完后要加#pragma pack(),释放内存对齐。为啥要以1个字节对齐呢?是因为一般的数据类型都是大于等于1个字节的。这样的话就会按照数据类型原有的的分配。就不会错位了。
#include <stdio.h>
#pragma pack(1) // 按照1字节对齐
typedef struct student
{
char a; //设置1个字节对齐,char 是1个字节,以1字节对齐,按1个字节处理
short b; //设置1个字节对齐,short 是2个字节,以1字节对齐,按2个字节处理
int c; //设置1个字节对齐,int 是4个字节,以1字节对齐,按4个字节处理
float d; //设置1个字节对齐,float 是4个字节,以1字节对齐,按4个字节处理
double e; //设置1个字节对齐,double 是8个字节,以1字节对齐,按8个字节处理
long f; //设置1个字节对齐,long 是4个字节,以1字节对齐,按4个字节处理
unsigned char g; //设置1个字节对齐,unsigned char 是1个字节,以1字节对齐,按1个字节处理
unsigned short h; //设置1个字节对齐,unsigned short 是2个字节,以1字节对齐,按2个字节处理
unsigned int i; //设置1个字节对齐,unsigned int 是4个字节,以1字节对齐,按4个字节处理
}STU;
#pragma pack() // 释放内存对齐
int main()
{
printf("%d.\n", sizeof(STU));
return 0;
}
这回就按照每个变量的类型给出实际的结果了,1+2+4+4+8+4+1+2+4=30。
4. #pragma pack(push)和#pragma pack(pop)
#pragma pack(push):编译器编译到此处时将保存对齐状态(保存的是push指令之前的对齐状态)。
#pragma pack(pop):编译器编译到此处时将恢复push指令前保存的对齐状态(请在使用该预处理命令之前使用#pragma pack(push))。
可以知道,当我们想要一个结构体按照4字节对齐时,可以采用以下两种方式:
方式一:
可以使用#pragma pack(4)
,最后又想使用默认对齐方式时,可以使用#pragma pack()
进行恢复。
#pragma pack(4)
struct {
...
}
#pragma pack()
#include <stdio.h>
#pragma pack(4)
typedef struct student
{
char a; // 1
short b; // 2
int c; // 4
float d; // 4
double e; // 8
}STU;
#pragma pack()
int main()
{
printf("%d.\n", sizeof(STU));
return 0;
}
方式二:
#pragma pack(push)
#pragma pack(4)
struct {
...
}
#pragma pack(pop)
这样在push和pop之间的结构体就可以按照pack指定的字节(这里是4字节对齐方式),而pop之后的结构体按照#pragma pack(push) 前的对齐方式进行对齐。
#include <stdio.h>
#pragma pack(push)
#pragma pack(4)
typedef struct student
{
char a; // 1
short b; // 2
int c; // 4
float d; // 4
double e; // 8
}STU;
#pragma pack(pop)
int main()
{
printf("%d.\n", sizeof(STU));
return 0;
}