C 内存对齐
对于程序员来说,最烦恼最耗时的工作莫过于与内存打交道,但是内存至关重要,不得不对其保持最大的警惕。
为什么需要内存对齐?
- 平台原因:不是所有的一欧诺个见平台都能访问任意地址上的任意数据的;某些硬件平台如果访问未对齐的地址,则会报出对齐错误,有些专业的处理器通常不支持访问未对齐地址。
- 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界对其。原因在于,为了访问未对齐的内存,处理器需要做两次内存访问,而对齐的内存访问仅需要一次。
计算机通常以字大小的块来处理内存,一个字是计算机数据的自然单位,通常由计算机架构决定。现代通用计算机通常是4个字节(32位)或8个字节(64位)。
假设处理器,一次读取4个字节的数据。且内存如图所示
若保存4字节的int
类型,不需要进行额外工作就可以正确对齐,因为int
数据类型大小与该架构的数据自然单元契合。
若我们放置一个char
, 一个short
, 和一个int
到内存中。原本应该得到的结果如下所示
这将需要两次内存访问,并且进行移位来获取int
数据。这将比内存对齐的数据至少花费两倍时间,因为计算机科学家们提出了内存对齐的方法。在本例中,添加一些padding
在第一个字节,确保有效对齐
上图所示被认为是自然对齐,编译器会自动添加正确的padding
根据目标平台。
内存对齐规则
默认对齐
如果有指定对齐字节数目,则编译器会按 类或结构中最大类型长度来对齐。可以通过语句#pragma pack(i)
来指定对齐字节数目,i的取值为1, 2, 4, 8, 16
对齐规则:
- 如果设置了内存对齐为 i 字节,类中最大成员对齐字节数为j,那么整体对齐字节n = min(i, j) (某个成员的对齐字节数定义:如果该成员是c++自带类型如int、char、double等,那么其对齐字节数=该类型在内存中所占的字节数;如果该成员是自定义类型如某个class或者struct,那个它的对齐字节数 = 该类型内最大的成员对齐字节数)
- 每个成员对齐规则:类中第一个数据成员放在offset为0的位置;对于其他的数据成员(假设该数据成员对齐字节数为k),他们放置的起始位置offset应该是 min(k, n) 的整数倍
- 整体对齐规则:最后整个类的大小应该是n的整数倍
- 当设置的对齐字节数大于类中最大成员对齐字节数时,这个设置实际上不产生任何效果;当设置对齐字节数为1时,类的大小就是简单的把所有成员大小相加
示例
Example 1
未指定对其字节,则n = 4
即最大成员int
的大小
struct Foo{
char x; // 1 byte 放在偏移为0的地址,位置区间为[0]
short y; // 2 bytes 起始位置应该是2的倍数,即2, 位置区间为[2, 3]
int z; // 4 bytes 起始位置应为4的倍数,位置区间为[4, 7]
};
此时成员共占用[0-7] 8
个字节,还需整体对齐,大小应该是4
的倍数,即8
Example 2
假设指定对其字节为8
, 那么n = min(8, 8) = 8
struct Foo{
char x; // 1 byte 起始位置为0, 位置区间为[0]
double y; // 8 bytes 起始位置应为8的倍数,即8, 位置区间为[8, 15]
char z; // 1 byte 起始位置应为1的倍数,即16, 位置区间为[16]
}
此时成员共占用17
字节,还要整体对齐,为8
的倍数,故大小为24