【问题标题】:Data Alignment: Reason for restriction on memory address being multiple of data type size数据对齐:限制内存地址为数据类型大小的倍数的原因
【发布时间】:2019-12-15 14:43:13
【问题描述】:

我了解数据对齐的一般概念,但我不明白的是对内存地址值的限制,强制为底层数据类型大小的倍数。

This answer很好地解释了数据对齐。

引用:

让我们看一个内存映射:

+----+
|0000| 
|0001|
+----+
|0002|
|0003|
+----+
|0004|
|0005|
+----+
| .. |

在每个地址都有一个可以单独访问的字节。但是只能在偶数地址处获取单词。因此,如果我们读取 0000 处的字,我们会读取 0000 和 0001 处的字节。但如果我们要读取位置 0001 处的字,则需要两次读取访问。首先是 0000,0001,然后是 0002,0003,我们只保留 0001,0002。

问题:

假设它是真的,为什么 " 但是单词只能在偶数地址获取。" 是真的吗?例子中的内存/堆栈指针不能指向0001,然后从那里读取一个信息字吗?

我们知道机器可以通过一次读取操作以 2 个字节的块读取内存(在示例中为 [0000, 0001] 或 [0002, 0003])。因此,如果我的地址寄存器指向 0001(奇数地址而不是偶数地址),那么我可以在一次读取操作中直接从那里读取 2 个字节(即 0001 和 0002),对吗?

【问题讨论】:

  • Purpose of memory alignment 及其答案有帮助吗?
  • @user4581301 我确实看过这个。它确实概述了问题,但没有解决内存地址多重性限制。谢谢
  • 想想第一个答案关于速度的部分所涉及的性能问题:如果数据没有对齐,可能需要多次读取。如果硬件排队读取 16 位块,对于未对齐的数据,CPU 可能必须读取 16 位,保留高 8 位,读取 16 位,保留低 8 位,组装并放入寄存器。这很糟糕。许多 CPU 通过拒绝这个问题将它扼杀在萌芽状态。那些允许它接受性能打击的人
  • @user4581301 是的,我明白你想说什么。链接的答案说类似。但是,我的问题是关于不同的问题;地址本身的问题,而不是指向的数据,是数据大小的倍数。如果不是这种情况,我可以简单地移动地址并在一次读取中提取保存我的适当大小数据的内存块。 (正如我在问题的后半部分所说的)
  • @User10482 我不明白您在找到和获得的答案与您提出的问题之间有何区别,您似乎认为它们不同,但事实并非如此。

标签: c++ system compiler-optimization


【解决方案1】:

关于该陈述的假设不一定正确。我不想重复您与描述使用对齐访问的原因和高度偏好对齐访问的原因相关的答案,但是有些架构确实支持未对齐的内存访问——例如 ARM(查看此SO answer)。

但我认为,您的问题实际上归结为硬件架构,特别是数据总线设计,以及各个芯片制造商的工程师设计的随附指令集。


某些 Cortex-M 内核明确允许您通过配置使用错误寄存器启用 CPU 在未对齐的访问时触发异常,这意味着您可以在罕见的用例中“利用”未对齐的内存访问。

【讨论】:

  • 除非在具有非常不寻常的硬件的系统中,您不能通过单次访问进行未对齐的访问。 ARM 允许非对齐访问,但这是有代价的并且有一些注意事项。
【解决方案2】:

通常处理器内部地址指向一个完整的字。这是因为您不希望您的(简单)处理器能够以随机字节(或更糟:位)寻址一个字,因为

  1. 您浪费了可寻址内存:假设您的处理器能够处理的最大可能地址是其字长的最大值,您可以将其乘以字长来计算您可以使用的存储量地址。 (每个唯一的地址都指向一个完整的单词)我在这里所说的“地址”不一定看起来像可能存储在高级编程语言的指针中的地址。指针地址寻址每个字节,编译器或解释器将其解释为相应的汇编指令(从加载的字中丢弃不需要的字节)

  2. 从内存中加载的字可以是任何东西、一个值或您在处理器上运行的程序的下一条指令 - 加载到处理器中的前一个字通常会指示接下来加载的字是什么用于:另一条指令(例如算术运算、加载或存储指令),后面可能跟有操作数(值或地址)。能够处理未对齐的单词会使处理器复杂化很多简单的单词。

【讨论】:

  • 关于 1. 仍然可以通过特定的汇编指令从加载的字中检索任何字节:loadLeastSignificantByte $addressOfWord
【解决方案3】:

假设它是真的,为什么“但是单词只能在偶数地址获取。”是真的?

内存实际上存储单词。处理器实际上是用字来寻址内存,并且一次取一个字。

当您获取一个字节时,它实际上是获取一个字,然后忽略前半部分或后半部分。

在 32 位处理器上,它获取一个 32 位字,然后忽略四分之三;在 32 位处理器上获取 16 位字会忽略一半字。

如果您要获取的 16 位字(在 16 位处理器上)未对齐,则处理器必须获取两个字,取每个字的一半,然后重新组合它们。因此,即使在可以工作的处理器设计上,它通常也会更慢。

许多处理器设计都不会打扰 - 要么就是不允许这样做,要么会强制操作系统处理它(这很慢)。

(并非所有类型的处理器都以这种方式工作 - 例如,8 位处理器通常一次获取一个字节)

示例中的内存/堆栈指针不能指向0001,然后从那里读取一个信息字吗?

如果处理器支持,是的。

【讨论】:

  • 好的,这是地址指针的某种硬件限制。应用到示例中,这意味着我可以指向 0000、0002、0004.... 但不能指向 0001、0003 等,对吗?)但是,实现一个可以指向任何地方的对象似乎很容易。只需将奇数地址存储在堆栈指针寄存器或任何地址寄存器中并读取它指向的内存。 (在它强制的任何大小的块中(示例中为 2 个字节)
  • @User10482 指针可以指向任何地方。但是假设您是一个连接到 16 位内存系统的 CPU(因此内存将地址 0000 视为 16 位字,地址 0001 视为不同的 16 位字,它们不会重叠)。当程序在 0000 处请求 16 位时,您从内存中请求 0000。当程序在 0002 处请求 16 位时,您从内存中请求 0001。当程序在 0003 处请求 8 位时,您从内存中请求 0001 并丢弃一半。当程序在 0003 处请求 16 位时,你会怎么做?
  • (因此内存将地址 0000 视为 16 位字,将地址 0001 视为不同的 16 位字,它们不重叠) 现在很有意义。该程序将内存视为唯一寻址的 1 字节大小的块,但处理器将其视为按顺序编号的 2 字节块。现在我看到了问题!谢谢。
  • @User10482 它可能会比这更疯狂。多亏了虚拟内存,您在程序中看到的地址可能与程序内存在存储中的实际布局方式完全不同。
  • 当然他们这样设计内存系统的原因是因为获取 2 个块的时间是获取 1 个块的两倍,无论块有多大。所以他们想要真正的大块。 (现在,大于 16 位)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-10-26
  • 1970-01-01
  • 2016-02-20
  • 1970-01-01
  • 1970-01-01
  • 2023-03-31
  • 1970-01-01
相关资源
最近更新 更多