内存寻址和堆栈对齐 - 我理解正确吗?

时间:2015-08-21 15:51:28

标签: c memory-management stack memory-address memory-alignment

我正在尝试深入研究内存分配,寻址,并且我也遇到了堆栈对齐的概念,以及通常的内存对齐。我想了解我是否正确理解了所有概念。我的问题都是现在的计算机和处理器,比如我们笔记本电脑上的那些。我想强调我在stackoverflow上阅读了很多其他问题,而且我的大部分实际知识都来自于他们。

我的第一个疑问与记忆词的概念有关。存储器字不仅定义寄存器和总线大小,还定义基本存储器单元(例如,64位拱上的64位,32位拱上的32位)。但是,据我所知,无论内存字的大小如何,每个地址都会精确地引用 1字节的内存。所以我们可以说每个字节都有自己的地址。但是:

1) CPU无法访问单个字节但访问包含该字节的整个字是否正确?因此,如果请求某个特定字节(例如,我访问 char 的直接地址),它会访问整个字并进行一些计算以删除其他部分并返回确切的字节?

2)那么CPU是否可以实际访问存储器字单元并且每个存储器字从偶数地址开始,多个单元本身?

因此,例如,在64位架构上,存储器字是8字节,因此(示例)地址0x2710(基数10中的10000)将是存储器字的开始。如果我尝试访问0x2711,CPU将从0x2710访问到0x2717,然后只提取单个字节。正确的吗?!

二。正如我之前所说,我遇到了内存对齐。一开始它给我带来了一些困惑。如果我做对了,请帮助我理解。问题本质上与性能有关,或者在某些情况下,与需要16字节对齐的SSE特定指令有关。在第一种情况下,例如,If(在64位拱门上)在2个存储字中存储8字节数据(例如,长整数),CPU需要2次访问而不是1次访问。

3)因此,请采用以下示例:

0x2710 | .... |
0x2711 | .... |
0x2712 | .... |
0x2713 | .... |
0x2714 | data |
0x2715 | data |
0x2716 | data |
0x2717 | data |
0x2718 | data |
0x2719 | data |
0x2720 | data |
0x2721 | data |
0x2722 | .... |
0x2723 | .... |
0x2724 | .... |
0x2725 | .... |

在这种情况下,内存未对齐。正确?通过对齐,CPU将仅存储来自0x2710的数据,或者如果占用直到0x2713,则它将插入填充,然后存储来自0x2718的8字节数据。正确?

4)因此,内存对齐基本上包括故事多字节数据,仅从所需字节单元的多个地址开始(通常是存储器字本身,但也包括其他自定义单元 - 例如,在GCC上使用mpreferred-stack-boundary)。我说“多字节”数据,因为如果数据只有一个字节,它将始终只适合一个字。这一切都正确吗?

5)编译器是否应用了内存对齐?因此,它是应用于二进制(汇编)代码还是在存储数据时由CPU以某种方式应用? 而且,这不是对记忆的大浪费吗?我的意思是,如果总是应用它,每个多字节数据也可能意味着填充!我可能会浪费大量内存空间!

这就是全部!谢谢,非常感谢提前!

2 个答案:

答案 0 :(得分:2)

数据对齐是硬件架构优化,并且特定于CPU。在单次提取中从内存中获取8个字节可以更快更简单,然后丢弃额外数据并在CPU内部移动数据。它还可以通过忽略地址的底部3位(0-7)使数据总线更简单,从而节省CPU / MMU和存储器总线之间的三条信号线。更少的数据总线意味着更容易在PCB上布线信号,并且RF噪声更小。

但是,如果CPU使用8字节对齐并将8字节值存储在未对齐的地址,则访问该值现在需要2次提取 - 导致执行性能较差。如果程序员/编译器知道数据对齐,他/它可以安排数据以避免双重提取。这可能会浪费一些内存来节省CPU周期,如果内存便宜而且时间不是很好,这很好。或者,如果内存不便宜,程序员可以使用#pragma pack(1)覆盖默认数据对齐,这会告诉编译器忽略数据对齐。

堆栈通常是对齐的,以便使用通用指令更容易推送和弹出。在这种情况下,它被用来使生活变得更加简单,代价是浪费了大量的记忆。

3)CPU不决定数据的存储位置,程序员/编译器(有时是OS)做出决定。 CPU完全能够从任何地址读取任何大小的数据,但不一定能在单个操作中读取。对齐不良的数据将需要更多的提取和更多的时间。有些CPU会在未对准的操作(Motorola 68000和许多低成本微控制器)上出现故障,但大多数带有MMU的CPU都会在内部处理它。

4)不完全。重要的是多字节数据不跨越对齐边界。在具有2字节值的8字节对齐的情况下,该值可以存储在地址0x1000,0x1001,0x1002,0x1003,0x1004,0x1005,0x1006处,而不需要多次提取。只将其存储在0x1007会导致问题,因为CPU需要获取0x1000 [.. 0x1007]和0x1008 [.. 0x100F]来读取整个值。

5)是的,有些记忆可能会被浪费,但并不多。当您只需要1时,读取8个字节没有性能损失。如果您的代码有8个char值,编译器将对它们进行排列,使它们都在相同的8字节字中。这样就不会浪费空间,也不会影响性能。

答案 1 :(得分:1)

每个平台都有一组称为ABI或应用程序二进制接口的约定。它们通常记录在平台开发人员提供的文档中。这些约定涵盖了许多主题,对齐规则就是其中之一。在给定的硬件架构上可能存在多个平台;一个例子是x64,其中有两个主要的ABI,Microsoft ABI(在Windows上使用)和System V ABI(在Linux上使用)。

对齐规则通常由硬件决定。例如,某些硬件架构显然无法在CPU内核和内存之间传输未对齐的数据。一些硬件架构虽然能够这样做,但每次这样的传输都会导致性能损失。

为了生成符合目标平台的ABI的程序,编译器工具链与操作系统协作。例如,OS保证始终将可执行文件部分加载到满足ABI强加的最严格的对齐要求的地址。当链接器生成包含对齐对象的节时,它依赖于它。 C编译器应使用它们的对齐要求来注释目标文件中的部分,以便链接器可以在使用多个编译单元组成单个文件时使用该信息进行相应的布局。

说到堆栈,可能存在不同的策略。在某些平台上,总是需要一个函数以最严格的对齐要求的倍数来堆栈。如果编译器可能依赖于它,它将相应地布置函数的堆栈帧。

但是,在某些平台上,堆栈对齐要求并不那么严格。例如,一个SSE数据类型以32个字节对​​齐,但是对于每个函数来说,要求以32个字节的多个字符吃掉它是太奢侈的:该类型的使用相对较少。这意味着,在编译将__m256置于堆栈上的函数时,编译器通常不会依赖于在函数启动时堆栈足够对齐。然后,编译器将在prolog中插入代码以检查它是否是,并且如果不是则另外增加堆栈。显然,这是一个权衡:如果你需要更严格的对齐,你的程序开始浪费太多的堆栈空间,如果要求太宽松,编译器将需要发出对齐代码,这会使代码膨胀并妨碍性能。 / p>

相关问题