为什么要内存对齐
cpu在读取内存时是一块一块进行读取的,块的大小可以是2,4,8,16(总之是2的倍数)。
64位系统默认是8字节,块的大小也可以自己用宏定义:#pragma pack(nSize);
CPU和内存IO的硬件限制导致没办法一次跨在两个数据宽度中间进行IO。
假如对于一个c的程序员,如果把一个bigint(64位)地址写到的0x0001开始,而不是0x0000开始,那么数据并没有存在同一行列地址上。因此cpu必须得让内存工作两次才能取到完整的数据。效率自然就很低
举例来说:如果一个变量int 的起始地址偏移是1,那么CPU要取这个地址上的数据,需要取两次。
如图,cpu按四字节读取,第一次读取0x0 ~ 0x3,
cpu第二次读取0x4 ~ 0x7,这时cpu才能读到这个int的值。
如果内存对齐了呢,就如下只需一次读取:
内存对齐的规则
内存大小的计算
因为内存对齐的存在,申请的内存往往会比实际使用的内存大,比如
typedef struct { int a; //4字节 double b; //8字节 short c; //2字节 }A; typedef struct { int a; //4字节 short b; //2字节 double c; //8字节 }B; sizeof(A)=24 sizeof(B)=16
上面两个结构体,理论上来说它们是一样大小的,但是当我们sizeof(A)=24,sizeof(B)=16。
分析一下:对结构体A来说,a占4个字节,占从0 ~ 3的字节,b是double类型占8个字节,占从8 ~ 15的字节,c占两个字节,
从16 ~ 17的字节。 对结构体B来说,a占4个字节,从0 ~ 3,b占两个字节从4 ~ 6;c占8个字节从8 ~ 15。当然上面的分析是针对gcc编译器的,不同的编译器有不
同的对齐规则。
扩展1:如果不强制对地址进行操作,仅仅只是简单用c定义一个结构体,编译和链接器会自动替开发者对齐内存的。尽量帮你保证一个变量不跨列寻址。
扩展2:其实在内存硬件层上,还有操作系统层。操作系统还管理了CPU的一级、二级、三级缓存。实际中不一定每次IO都从内存出,如果你的数据局部性足够好,那么很有可能只需要少量的内存IO,大部分都是更为高效的高速缓存IO。但是高速缓存和内存一样,也是要考虑对齐的。
参考博客:https://www.jianshu.com/p/cc0d1d627b73 https://www.jianshu.com/p/37409be16a37