内存地址对齐是一种安排和访问计算机内存中数据的方法。它由两个独立且相互关联的部分组成:基本数据对齐和结构数据对齐。当今的计算机在计算机内存中读写数据时以字长为单位进行操作(在32位系统中,数据总线宽度为32,并且每次可以读取4个字节,而地址总线宽度为32,因此最大寻址空间为2 ^ 32 = 4GB,但是最低的2位A [0],A [1]不用于寻址,A [2-31]只能连接到内存,因此只能访问4个空间,但总寻址空间仍为2 ^ 30 *字长= 4GB,因此,存储在存储器中的所有基本类型数据的首地址的最低两位为0(结构中的成员变量除外)。数据对齐的基本类型意味着存储器中数据的偏移地址必须等于一个字的倍数。这种存储数据的方式可以提高读取数据时的系统性能。为了对齐数据,可能有必要在前一个数据的末尾和下一个数据的开始处插入一些无用的字节。这是结构数据对齐。
例如,假设计算机的字长为4个字节,那么内存中变量的首地址满足4地址对齐方式,并且CPU每次只能读取4的倍数的地址。它可以读取4个字节的数据。假设整数数据a的第一个地址不是4的倍数(如下图所示),可以将其设置为0X00FFFFF3,然后将整数数据存储在地址空间为0X00FFFFF3〜0X00FFFFF6的存储空间中,并且仅CPU可以读取4的倍数的内存地址,因此,如果要读取a的数据,则CPU必须在0X00FFFFF0和0X00FFFFF4读取两次内存,并且还要对两次读取的数据进行处理才能获得,并且程序的瓶颈通常不是CPU的速度,而是取决于内存的带宽,因为CPU的处理速度比从内存中读取数据的速度快得多,因此降低了访问内存空间是提高程序性能的关键。从上面的示例可以看出,采用内存地址对齐策略是提高程序性能的关键。
示例:
首先,让我们看一下以下C语言结构:
typedef struct MemAlign { int a; char b[3]; int c; }MemAlign;
以上结构占用多少内存?也许您会说这很简单。计算每种类型的大小并将它们加在一起。以32平台为例,int类型占用4个字节,而char占用1个字节,因此:4 + 3 + 4 =11。然后此结构总共占用11个字节的空间。好吧,然后我们通过实践证明它是否正确,我们使用sizeof运算符查找该结构占用的内存空间大小sizeof(MemAlign),出乎意料的是,结果是12?看来我们错了吗?当然不是,但是这种结构已经过优化。此优化的另一个名称为“ Alignment”。那么这种对齐方式做了什么样的优化呢?让我慢慢解释。在解释之前,让我们看一张照片。 ,图片如下:

我相信学习汇编的朋友对此幅照片很熟悉。此图是CPU和内存如何交换数据的模型。其中,左边的蓝色框是CPU,右边的绿色框是内存。上面的0〜3是内存地址。在这里,我们的图片由32位CPU表示。我们都知道32位CPU使用DWORD作为数据传输的单位。正是由于这个原因,引起了另一个问题。那么问题是什么呢?问题是,由于32位CPU以双字形式传输数据,如果我们的数据只有8位或16位数据,CPU是否根据我们数据的位数进行数据传输?答案是不。如果这会使CPU硬件变得更加复杂,则32位CPU会以双字方式传输数据,而不管它是8位还是16位数据传输。没关系,可以传输8位或16位,但是事情并没有我们想象的那么简单。例如,如果在上图中将4字节的int类型数据放置在内存地址1的开头,则该数据将被占用。内存地址为1〜4,则此数据分为2部分,一部分在地址0〜3中,另一部分在地址4〜7中,由于32位CPU以双字传输,因此,一旦首先读取地址0至3中的内容,CPU将被读取2次。 ,然后再次读取地址4到7中的数据。最后,CPU提取并组合正确的int类型数据,并丢弃不相关的数据。反过来说,如果我们将这个int类型的4字节数据放在上图中从地址0开始的位置,该怎么办?读完此书后,也许您了解了,CPU只能通过读取一次来获取此int型数据。是的,就是这样。这次,CPU仅花费一个周期来获取数据。这说明了存储数据的重要性。放置正确的位置可以减少对CPU资源的使用。
那么,内存对齐的原理是什么?我总结了一下,大致分为三个部分:
第1条:第一个成员的第一个地址为0
第2条:每个成员的第一个地址是其自身大小的整数倍
第二个补充:以4字节对齐为例,如果其大小大于4字节,则对齐基于4字节的整数倍。
第三篇文章:最后,整个结构是对齐的。
第三个补充:以4字节对齐为例,采用结构中最大成员类型的倍数。如果超过4个字节,则对齐基于4个字节的整数倍。 (该项目的名称也为“互补”。互补的目的是在多个结构变量彼此相邻放置时满足对齐要求。)
以上三个原则听起来很抽象,所以让我们使用一个示例来加深对内存对齐概念的理解。以下是结构。让我们算出以下结构占用多少内存?假设我们在32位平台上并对齐4个字节:
#pragma pack(4) typedef struct MemAlign { char a[18]; double b; char c; int d; short e; }MemAlign;
下图显示了对齐的结构,如下所示:

我们将使用这张图片来说明如何对齐:
第一个成员(字符a [18]):首先,假设我们将其放在内存起始地址0。由于第一个成员占用18个字节,因此第一个成员占用内存地址,范围为0-18。
第二个成员(双精度b):由于双精度类型占用8个字节,并且由于8个字节大于4个字节,因此4字节对齐方式是基准。由于第一个成员的结束地址是18,所以地址18不是4的整数倍。我们需要再添加2个字节,即,将第二个成员放在地址20处。
第三个成员(char c):由于char类型占用1个字节,因此任何地址都是1个字节的整数倍,因此我们可以将其直接放置在第二个成员之后。
第四个成员(int d):由于int类型占用4个字节,但是地址29不是4的整数倍,因此我们需要再添加3个字节,即将该成员从地址32放置。
第五个成员(short e):由于short类型占用2个字节,因此地址36恰好是2的整数倍,因此我们可以直接填充它而不填充字节,只需跟随它即可。
这样,我们的内存对齐就完成了。但这距成功仅一步之遥,这是什么?是的,它是填写整个结构,然后我们将填写整个结构。因此,让我们先回顾一下补码的原理:“以4字节对齐为例,以结构中最大成员类型的倍数为例,如果超过4个字节,则全部基于4字节的整数倍对齐。”结构中最大的类型是双精度类型(占用8个字节),并且由于8个字节大于4个字节,因此我们仍然使用4个字节作为基线。整个结构的结束地址是38,而地址38不是4的整数倍,因此我们需要添加额外的2个字节来填充结构。下图中的红色是填充的空间:

到目前为止,我们的内存对齐和填充已完成!接下来,我们用实验来证明事实,过程如下:
#include#include // 由于VS2010默认是8字节对齐,我们 // 通过预编译来通知编译器我们以4字节对齐 #pragma pack(4) // 用于测试的结构体 typedef struct MemAlign { char a[18]; // 18 bytes double b; // 08 bytes char c; // 01 bytes int d; // 04 bytes short e; // 02 bytes }MemAlign; int main() { // 定义一个结构体变量 MemAlign m; // 定义个以指向结构体指针 MemAlign *p = &m; // 依次对各个成员进行填充,这样我们可以 // 动态观察内存变化情况 memset( &m.a, 0x11, sizeof(m.a) ); memset( &m.b, 0x22, sizeof(m.b) ); memset( &m.c, 0x33, sizeof(m.c) ); memset( &m.d, 0x44, sizeof(m.d) ); memset( &m.e, 0x55, sizeof(m.e) ); // 由于有补齐原因,所以我们需要对整个 // 结构体进行填充,补齐对齐剩下的字节 // 以便我们可以观察到变化 memset( &m, 0x66, sizeof(m) ); // 输出结构体大小 printf( "sizeof(MemAlign) = %d", sizeof(m) ); }
在程序运行期间,按以下方式检查内存:

其中,以各种颜色表示的下划线表示每个成员变量,蓝色框表示在内存对齐期间填充的多余字节。由于此处看不到填充效果,因此请看下图。篮子所包围的字节是与上图交叉处之外的部分,是填充的字节。

最后,我要说的是补语的作用。补码实际上是使此结构定义的数组变量也满足数组内部的内存对齐要求,以便更好地理解这一点。 ,我制作了一张图片以与本示例进行比较:

参考链接:
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/shoujiruanjian/article-364162-1.html
直接成为一艘在南海核心地区用不沉没的航母
刺杀总统本来就是刑事
不是中国大陆给你们撑腰