转换不同数据类型时的内存对齐

Memory alignment when casting different data types

我很好奇,为什么我的代码在 x86 和 armeabi 平台上有不同的行为。代码的概念(不是真正的代码,但足以理解问题):

struct data
{
   int x;
}

void method(unsigned char* buff)
{
   data D;

   memcpy(&D.x, buff, sizeof(int)); //good approach
   D.x = *(int*)buff; //bad approach
}

因此,当此代码由 GCC 在 arm 架构下编译时 - 它会导致 SIGFAULT(未对齐的内存)与数据类型的转换一致,尽管 msvc 编译的代码 运行 正常。据我所知,在这种情况下唯一正确的解决方案是使用 memcpy。有人能解释一下运行时到底发生了什么吗?

这是一个基本的硬件限制。

一些 CPUs 只能执行硬件指令,这些指令只能在适当对齐的地址上访问 2、4(或 8)字节值。也就是说,它们无法从奇数内存地址读取 4 字节值(例如)。该操作生成一个硬件异常,该异常被转换为一个信号。所有对 4 字节值的访问都必须来自被 4 整除的物理地址(例如)。

确切的对齐限制因 CPU 而异。 CPU 就是这样设计的。这是一个理论上的例子。假设在给定的硬件平台上,所有 RAM 都以 32 位字的形式访问。从编程的角度来看,它仍然是 8 位字节,但每个 RAM 字包含四个字节。 CPU 影响单个字节的指令由 CPU 执行,方法是获取包含该字节的整个内存字,执行操作,然后将其存储回去。但是,例如,影响 4 字节整数的操作预计会引用该内存字中第一个字节的逻辑地址。 CPU 获取整个 4 字节字,执行操作,然后将其存储回去。

所以最终结果是 CPU 无法寻址不是从偶数 4 字节边界开始的 4 字节值。从理论上讲,这可以通过获取两个相邻的内存字,执行影响与两个字重叠的 4 字节值的操作,然后将它们存储回 RAM 来实现。当然,这会增加严重的复杂性,有些 CPU 根本就不是为了这样做而设计的。

在您的示例中,指针取消引用转换为直接 CPU 指令,如果实际内存地址为奇数(例如),该指令将失败。 memcpy() 进行逐字节复制,并且有效。

C++11 有 alignof operator which you want to use (because, as , the hardware & instruction set and ABI have alignment restrictions). And it has also the alignas 说明符。

顺便说一句,像您一样使用 memcpy 是可能的,但可能效率低下(因为未对齐的数据移动比对齐的数据移动慢)。即使在可能存在未对齐访问 (à la D.x = *(int*)buff;) 的硬件(尤其是 x86)上,它也是低效的(并且可能慢 10 倍)。

事实上,您希望确保对 method 的每次调用都提供适当对齐的缓冲区。您可以使用 alignas & alignof,使用一些 union 等等...为此。

阅读更多关于 CPU cache. Look also at the answers section of http://norvig.com/21-days.html 的内容(这是一本非常有趣的读物)。