前提:为了访问速度和效率,需要各种类型数据按照一定的规则在空间上排列;
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取
某些特定类型的数据,否则抛出硬件异常。
为了访问未对齐的内存,处理器需要作两次内存访问;⽽对齐的内存访问仅需要
⼀次访问。
于是有了字节对齐,4个字节是一个自然对齐
为什么是4个字节?
32位机,即计算机数据总线宽度为32个,一次可以处理32位bit(即4个字节)
不论是结构体(struct)或者联合(union)在内存分布时都会有字节对齐,首先数据类型分浮点型,整型,字符类型,
我绘制下面一张表格(针对32位机)
下面是整个测试代码(vs2013运行,方便监视),我将分点论述。
typedef struct A
{
int a;
double b;
char c;
}A;
#pragma pack(4)
typedef struct AA
{
int a;
double b;
char c;
}AA;
#pragma pack()
typedef struct B
{
double b;
int a;
char c;
}B;
typedef struct C
{
char c;
int a;
char b;
}C;
typedef struct D
{
char c;
char b;
int a;
}D;
int main()
{
size_t sizeA = sizeof(A);
size_t sizeB = sizeof(B);
size_t sizeC = sizeof(C);
size_t sizeD = sizeof(D);
size_t sizeAA = sizeof(AA);
system("pause");
return 0;
}
监视结果
整个测试中,A和B形成对比,C和D形成对比。
首先, 结构体或类的自身对齐规则:应该对齐 其成员中自身对齐值最大 的那个值。
一
typedef struct A
{
int a;
double b;
char c;
}A;
结构体A,int占4字节,在这个结构体中,应该对齐double类型,所以占据空间8个字节(后4个空置),double占8字节,char类型情况同int类型 最终 占 8+8+8=24字节。
二
typedef struct B
{
double b;
int a;
char c;
}B;
结构体B内成员和A一样,更换顺序后,double占8字节,int应该和double对齐,提取8个字节空间,空余4个字节空间。但是char类型只占1个字节,可以利用int后空余空间,占据1字节
最终结构体B大小为8+8=16
三
typedef struct C
{
char c;
int a;
char b;
}C;
结构体C ,最大为int 对齐空间为4个字节,char c 占1个字节,后面3个字节的空缺,int占4个字节, char b同char c,最终4+4+4=12字节
四
typedef struct D
{
char c;
char b;
int a;
}D;
结构体D,是对C的优化,同例子二B对A的优化,char c和char b共同占据4个字节,int占据4个字节,4+4=8字节
对其扩展
typedef struct E
{
char c;
char b;
char f;
char g;
int a;
}E;
结构体E也只占据8个字节;
接下来我说一下program pack。
#pragma pack
使用指令#pragma pack (n),编译器将按照 n 个字节对齐。
使用指令#pragma pack (),编译器将取消自定义字节对齐方式。
在#pragma pack (n)和#pragma pack ()之间的代码按 n 个字节对齐。
字节对齐,我将另起炉灶,在另外一篇博客中归纳总结。
#pragma pack(push) //保存当前对其方式到 packing stack
#pragma pack(push,n) 等效于
#pragma pack(push)
#pragma pack(n) //n=1,2,4,8,16 保存当前对齐方式,设置按 n 字节对齐
#pragma pack(pop) //packing stack 出栈,并将对其方式设置为出栈的对齐
#pragma pack()来改变编译器的默认对齐方式。
#pragma pack(4)
typedef struct AA
{
int a;
double b;
char c;
}AA;
#pragma pack()
结构体AA和结构体A的内容和顺序都是一样的,但是AA的大小为16个。
这是由于 #pragma pack(n)预编译时,每一次存储,成员的大小都会和n来对比,成员数据类型对齐数 小于n,则默认编译,若大于或等于(>=)n,则按n字节存储(不合理的n有时会被编译器优化,linux则会默许程序员的预编译处理)。
AA中 自身对齐数为8字节,大于4 ,int存储在4个字节大小,double本身为8字节,无法改变,存储8个字节,c占1个字节,存储pack的n字节数,占4个字节,最终 4+8+4=16字节
当然,pack可以设置为pack(1),最终只占13字节,但是我们的机器是32位机器,在保证空间的同时也要保证效率。
下面是对本文的浓缩概括。
一个字或双字操作数跨越了 4 字节边界,或者一个四字操作数跨越了 8 字节边界,被认为是未对齐的,从而需要两次总线周期来访问内存。
一个字起始地址是奇数但却没有跨越字边界被认为是对齐的,能够在一个总线周期中被访问。
某些操作双四字的指令需要内存操作数在自然边界上对齐。如果操作数没有对齐,这些指令将会产生一个通用保护异常。
双四字的自然边界是能够被 16 整除的地址。其他的操作双四字的指令允许未对齐的访问(不会产生通用保护异常),然而,需要额外的内存总线周期来访问内存中未对齐的数据。
缺省情况下,编译器默认将结构、栈中的成员数据进行内存对齐。
参考链接:https://blog.csdn.net/weixin_41143631/article/details/81221777