原理
在特定平台特定读取模式下,cpu并不会随机读取内存,比如读取结构体数据时,会以结构体首地址作为参考点,位移x*n(n为处理字长,这个跟编译指令里设置字节对齐值不是一个概念),故一但出现读取奇数地址且长度大于1的数据时,就可能会出现读取一个数据会读取多次的现象,通常,编译器会对结构体的每个字段进行字节对齐,使其达到n,一次即可读取,从而提高cpu读取效率。
优缺点
优点:
- 提高读取特定数据效率
- 运用得当,在默认情况下能节省空间
缺点:
- 占用额外无用空间,这也是空间换时间的弊端
应用场景
编译器所做的字节对齐优化,在特定场景中,我们并不期望有。比如在网络协议模块,由于这些协议字段经常会有变动,但如果按照默认对齐方式的话,程序修改会很麻烦。比如客户端和服务端的对接会遇到问题,不同平台很难确定读取或是写入的地址。因此,通常服务协议的结构体都不按默认对齐方式,你会看到有:
#pragma pack(push,1)
...
#pragma pack(pop)
这一对编译指令,这样修改协议会简单很多。那为什么默认对齐字节不是1而是4呢?这样不仅省空间,还能解决一些麻烦的问题。笔者在原理中已经提到了,在特定平台特定的读取模式下,这样可能会降低cpu读取内存的效率。
例子
先介绍三个概念:自身对齐值、指定对齐值、有效对齐值。
自身对齐值:数据类型本身的对齐值,例如char类型的自身对齐值是1,short类型是2;
指定对齐值:编译器或程序员指定的对齐值,32位单片机的指定对齐值默认是4;
有效对齐值:自身对齐值和指定对齐值中较小的那个。
对于结构体
1、不但结构体的成员有有效对齐值,结构体本身也有对齐值,这主要是考虑结构体的数组,对于结构体或者类,要将其补齐为其有效对齐值的整数倍。结构体的有效对齐值是其最大数据成员的自身对齐值;
2、存放成员的起始地址必须是该成员有效对齐值的整数倍。
具体看下下面的例子读者应该就会明白。
#include<iostream>
using namespace std;
#pragma pack(push,1)//可为1,2,4,8
struct s1
{
char c1;
char c2;
int x;
};
struct s2
{
char c1;
int x;
char c2;
};
struct s3
{
char c1;
int x;
short sx;
char c3;
};
int main()
{
char test;
cout << "s1:"<<sizeof(s1)<<endl;
cout << "s2:" << sizeof(s2) << endl;
cout << "s3:" << sizeof(s3) << endl;
system("pause");
return 0;
}
#pragma pack(pop)
当对齐字节为1时:
当对齐字节为4(默认)时: