Skip to content
发布日期:2021/03/28
阅读量:加载中...
标签:memory

最为一名程序员,你知道一个基础变量在计算机是怎样存储的吗?你知道一个整数占多大的空间?一个结构体占多大的空间?一个联合体占多个的空间?

对齐

​ 很多计算机系统对基础数据类型的可允许地址做了一个限制,要求某种类型的对象的地址必须是某个值k(通常是2,4,8)的倍数。这种对齐限制简化了处理器和存储器系统之间接口的硬件设计。假如,一个处理器总是从存储器中取8个字节的数据出来,则地址必须为8的倍数。如果我们能保证所有的double都将他们的地址对齐为8的倍数,那么就可以用一个存储器操作来读或者写值了。否则我们可能要执行两次存储器访问,因为对象可能分布在两个8字节存储器中。

​ 确保每种数据类型都是按照指定方式来组织和分配的,即每种类型的对象都满足它的对齐限制,就可以保证实施对齐。编译器在汇编代码中放入命令,指明全局数据所需要的对齐。当然我们自己在代码中也可以控制,这个后面在说。

对齐规则

​ 每个特定平台上的编译器都有自己默认的“对齐系数”。可以通过预编译命令 #pragma pack(n), n = 1,2,4,8来改变这一系数。若没有手动指定,那么编译器就会默认将成员变量中最大的类型字节数设置为对齐值:m

有效对齐值

​ 有效对其值:是给定值#pragma pack(n)和结构体中最长数据类型长度中较小的那个。有效对齐值也叫对齐单位

了解了上面的概念后,我们现在可以来看看内存对齐需要遵循的规则:

(1) 结构体第一个成员的偏移量(offset)为0,以后每个成员相对于结构体首地址的 offset 都是该成员大小与有效对齐值中较小那个(即·对齐单位)的整数倍,如有需要编译器会在成员之间加上填充字节。

(2) 结构体的总大小为 有效对齐值 的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。

示例

考虑下面的结构体,使用系统默认的设置,分析其内存分布

cpp
//ubuntu 64位,sizeof(int) 4字节
struct S1
{
    int i;
    char c;
    int j;
};

假设编译器用的是最小的9字节分配,画出来的图是这样的offset1.png

它是不可能满足域i(偏移量是0)和j(偏移量是5)的4字节对齐要求的。所有编译器在域c和域j之间插入了一个3字节的空隙(此处用xxx表示)。offset2.png

另外编译器可能需要添加一些填充到结构体的末尾,这样结构数组的每个元素都会满足它的对齐要求。

cpp
//ubuntu 64位,sizeof(int) 4字节
struct S1
{
    int i;
    int j;
    char c;
};

offset3.png

cpp
//ubuntu64位,sizeof(int) 4字节,sizeof(double) 8字节
union A{
    char c;
    int a[5];
    double d;
};
sizeof(A) = 24
cpp
struct B{
    char c;
    int a[5];
    double d;
};
sizeof(B) = 32

评论