首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >内存寻址和堆栈对齐-我正确理解了吗?

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

Stack Overflow用户
提问于 2015-08-21 15:51:27
回答 2查看 275关注 0票数 0

我试图深入研究内存分配、寻址,我还遇到了堆栈对齐的概念,以及通常情况下的内存对齐。如果我正确掌握了所有的概念,我想要理解。我的问题都提到了现在的计算机和处理器,比如我们的笔记本电脑。我想强调的是,我读到了很多关于堆叠溢出的问题,我的实际知识大多来自这些问题。

我最初的怀疑是关于记忆词的概念。存储器字不仅定义寄存器和总线大小,而且定义基本存储单元(例如,64位在64位arch上,32位在32位arch ecc上)。但是,据我所知,每个地址都准确地引用了内存的1字节,而不管内存字的大小如何。所以我们可以说每个字节都有自己的地址。但是:

1)不能访问单个字节,但访问包含该字节的整个单词,这是正确的吗?因此,如果一个特定的字节被请求(例如,我访问一个字符的直接地址),它访问整个单词并进行一些计算以删除另一部分并返回确切的字节?

2)那么,CPU实际上只能访问内存字单元,并且每个内存字从单元本身的多个偶数地址开始,这是正确的吗?

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

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

3)所以,以下面的例子为例:

代码语言:javascript
复制
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上使用多个堆栈边界)。我说的是“多字节”数据,因为如果数据只有一个字节,它总是只适合于一个单词。这些都是对的吗?

5)编译器应用了内存对齐吗?因此,它是应用在二进制(程序集)代码中,还是由CPU在存储数据时以某种方式应用?难道这不是在浪费记忆吗?我的意思是,如果它总是被应用,那么每个多字节数据也可能意味着一个填充!我可以是一个巨大的浪费记忆空间!

就这样!谢谢,真的谢谢你!

EN

回答 2

Stack Overflow用户

发布于 2015-08-21 17:01:10

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

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

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

3) CPU并不决定数据的存储位置,而是由程序员/编译器(有时是OS)作出决定。CPU完全能够从任何地址读取任何大小的数据,但不一定是在一次操作中。对齐不良的数据需要更多的获取和更多的时间。一些CPU会在错误的操作(摩托罗拉68000和许多低成本的微控制器)上出错,但大多数带有MMU的CPU将在内部处理它。

4)不完全是。重要的是多字节数据不能跨越对齐边界。如果使用2字节值对齐8字节,则该值可以存储在地址0x1000、0x1001、0x1002、0x1003、0x1004、0x1005、0x1006,而不需要多次获取。只有在0x1007存储它才会造成问题,因为CPU需要获取0x1000..0x1007和0x1008.0x100F来读取整个值。

5)是的,有些记忆可能会被浪费,但不会被浪费太多。当你只需要一个字节的时候,读取8个字节就没有性能上的问题。如果你的代码有8个char值,编译器会把它们排列起来,使它们都在同一个8字节的单词中。这样就不会浪费空间,也不会影响性能。

票数 2
EN

Stack Overflow用户

发布于 2015-08-21 17:17:08

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

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

为了生成符合目标平台ABI的程序,编译器工具链与操作系统协作。例如,操作系统保证可执行文件部分始终加载在满足ABI规定的最严格对齐要求的地址上。当链接器生成包含对齐对象的节时,它依赖于这一点。C编译器应注释对象文件中的部分及其对齐要求,以便链接器在从多个编译单元组合单个文件时可以使用这些信息来相应地列出内容。

当涉及堆栈时,可能存在不同的策略。在某些平台上,函数总是以最严格的对齐要求的倍数消耗堆栈。如果编译器可能依赖它,它将相应地布局一个函数的堆栈框架。

然而,在一些平台上,堆栈对齐的要求并不那么严格。例如,SSE数据类型是以32字节对齐的,但人们认为,对于每个函数来说,需要消耗32字节是太奢侈了:该类型使用相对较少。这意味着,在编译将__m256放在堆栈上的函数时,编译器可能通常不依赖于在函数开始时对齐堆栈。然后,编译器将在prolog中插入一段代码,以检查它是否是,如果不是,则另外增加堆栈。显然,这是一种权衡:如果需要更严格的对齐,程序就会开始浪费太多的堆栈空间,如果需求太宽松,编译器将需要发出对齐代码,这会使代码膨胀并影响性能。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/32144548

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档