关于C/C++的内存对齐

什么是内存对齐?

内存对齐就是编译器在对变量分配内存时会将每个数据单元放置在合适的位置上。内存对齐实例:

#include <iostream>
using namespace::std;
struct Memory{
    int a;
    char c;
}memo; 

int main(int argc, char *argv[]) {  
    cout << sizeof(memo) << endl;
    return 0;
}

在32位系统上char占一个字节,而int占4个字节,那么struct Memory应该是5个字节的,但是这里的输出结果却是8,这就是内存对齐的结果。原本我们想着存储的形式是这样的:(假设这里是默认的内存对齐字节:4)
这里写图片描述
但实际上内存对齐之后,它的存储方式是这样的:
这里写图片描述

那么为什么需要内存对齐呢?

1.平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2.性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

如果没有内存对齐,现在有一个int类型的值,它随意地存放在1地址开始的4个字节块中,那么它应该是这样的:
这里写图片描述
当处理器需要用到这个数据时,就从0地址开始读取,首先读取0-3这四个字节块,然后剔除了0字节块
这里写图片描述
然后读取4-8字节块,然后剔除5-8字节块,再把剩下地字节块合起来就是真正需要的,那么这样,读取一个数据就需要访问内存两次了。
这里写图片描述
这样的话,就会造成效率低下的问题,所以内存对齐是很有必要的。不过也由于内存对齐,导致会有空间上的浪费,例如这里的地址1-3就没有使用,所以这种做法是用空间来换时间的。
这里写图片描述

内存对齐的规则:

1、数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。
2、结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。
3、结合1、2可推断:当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。

我们将刚开始的代码修改以下,变成下面这样:

// #pragma pack的默认值是n = 4
#include <iostream>
using namespace::std;
struct Memory{
    char a;
    char b;
    int x;
}memo; 

int main(int argc, char *argv[]) {  
    cout << sizeof(memo) << endl;
    return 0;
}

那么它的结果是多少呢?没错,是8;计算的过程是这样的:
1. 首先是存a,根据规则1,将它放在地址0的字节块上:
这里写图片描述
2. 然后存放b,根据规则1,“以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行”,char的长度为1,pack的值为4,所以按1对齐,将b存放在地址1的字节块:
这里写图片描述
3. 最后存放x,同上,因为int = 4, pack的值n = 4,所以无所谓,用4来对齐,要求x存放的地址为n的整数倍,很明显将x放在地址4开始的地址块就可以了。
这里写图片描述
4. 然后根据规则2对整个结构体进行对齐,现在结构体是8,已经是4的整数倍,也就是已经对齐了,所以不需要调整,所以内存对齐的最后结果是8。

但是如果我们再修改以下代码:

#include <iostream>
using namespace::std;
struct Memory{
    char a;
    int x;  //注意这里的b与x的位置交换了;
    char b;
}memo; 

int main(int argc, char *argv[]) {  
    cout << sizeof(memo) << endl;
    return 0;
}

那么这样,内存对齐的结果应该多少呢?
答案是12;
那么这样又是怎么计算的呢?
1. 同样的,首先将a存储进地址0;
这里写图片描述
2. 然后存储x,上面已经说了,这里的对齐应该是4字节,n = 4的整数倍,很明显也要从地址4开始存储,占据4个字节;
这里写图片描述
3. 然后是存储b,占据一个字节块,字节对齐为1
这里写图片描述
4. 最后是对整个结构体进行内存对齐,原本我们占据到了地址8,总共9个字节块,那么最接近9的n = 4的整数倍是12,所以内存对齐的结果就是12。

当然,我们还可以指定内存对齐的字节数,在程序的头部使用:#pragma pack(n) (n=1,2,4,8,16…)
我们再对上面的程序进行修改:

#include <iostream>
#pragma pack(1)     //注意我们在这里对内存对齐的字节进行了指定
using namespace::std;
struct Memory{
    char a;
    int x;
    char b;
}memo; 

int main(int argc, char *argv[]) {  
    cout << sizeof(memo) << endl;
    return 0;
}

对于这个程序的运行结果就不再是12,而是6。而且再怎么更改结构体中变量的位置,也不会影响这个结果,因为此处对齐的字节是最小的了。指定为1也就相当于不进行内存对齐!

接下来顺便说一下union共用体的内存对齐(需要满足两个条件):

1.大小要足够容纳最宽的成员;
2.内存对齐的结果要能被其中的所有基本数据类型的成员整除;
#include <iostream>
using namespace::std;
union Student{
    char name[12];
    int age;
    double mark;
}student;

int main(int argc, char *argv[]) {  
    cout << sizeof(student) << endl;
    return 0;
}

在Student这个结构体中最大的成员是name,占据12个字节,但是12虽然可以被int的4个字节整除,但是不能被double的8个字节整除,所以需要对齐为16。所以这里的运行结果应该是16。

而对于enum来说,默认它的数据类型就是int的,所以对其进行sizeof运算,得出的结果就是4。

猜你喜欢

转载自blog.csdn.net/weixin_41713281/article/details/79825874