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

时间:2016-11-11 13:16:34

标签: c++ casting memcpy

我很好奇,为什么我的代码在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架构下编译的时候 - 虽然msvc编译的代码运行正常,但它会导致SIGFAULT(未对齐的内存)与数据类型的转换一致。据我所知,在这种情况下唯一正确的解决方案是使用memcpy。有人可以解释运行时真正发生的事情吗?

2 个答案:

答案 0 :(得分:2)

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

某些CPU只能执行仅在适当对齐的地址上访问2,4(或8)个字节值的硬件指令。也就是说,它们无法从奇数存储器地址读取4字节值(例如)。该操作会生成转换为信号的硬件异常。对4字节值的所有访问必须来自均匀分为4的物理地址(例如)。

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

因此最终结果是CPU无法寻址不在偶数4字节边界上启动的4字节值。从理论上讲,这可以通过获取两个相邻的存储器字来执行,执行影响与两个字重叠的4字节值的操作,然后将它们两者存储回RAM中。当然,这会增加很多复杂性,而一些CPU根本就不是为此而设计的。

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

答案 1 :(得分:0)

C ++ 11具有您要使用的alignof运算符(因为Sam Varshavchik answered,硬件& instruction setABI具有对齐限制)。它还有alignas说明符。

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

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

详细了解CPU cache。另请参阅http://norvig.com/21-days.html答案部分(这是一篇非常有趣的内容)。