非对齐内存访问是否总是会引起总线错误?

10
根据维基百科页面“Segmentation fault”,总线错误可能是由于内存访问不对齐引起的。该文章举了一个触发总线错误的例子。在这个例子中,我们需要启用对齐检查以查看总线错误。如果我们禁用这种对齐检查,会发生什么呢?
程序似乎能正常工作。我有一个程序频繁地访问未对齐的内存,并被很多人使用,但没有人向我报告总线错误或其他奇怪的结果。如果我们禁用对齐检查,未对齐内存的副作用是什么?
平台: 我在x86/x86-64上工作。我还尝试使用“gcc -arch ppc”在Mac上编译我的程序,它能正常工作。

你正在使用的平台是什么? - Frank Bollack
Pavel Minaev在很大程度上回答了我的问题。我正在使用x86 / x86_64。我尝试通过在Mac上使用“gcc-arch ppc”编译它来运行我的程序,并且它可以正常工作。 - user172818
请注意,根据C标准,未对齐的内存访问(实际上,甚至只是指针赋值)是未定义行为 - 因此,如果您这样做,符合规范的编译器可以执行任何操作(尽管并非所有编译器都会采取这种自由)。 - sleske
相关:违反alignof(T)是未定义行为,即使在x86上也可能导致现实世界中的问题,例如当自动向量化编译器假定16字节对齐边界与一些short之间的整数倍时,就会出现问题:为什么对mmap'ed内存的不对齐访问有时会在AMD64上导致段错误? - Peter Cordes
3个回答

14
  1. 访问不对齐的内存可能会明显地变慢(即几倍于正常速度)。

  2. 并非所有平台都支持不对齐访问 - 例如,ia64 (Itanium) 不支持,但 x86 和 x64 支持。

  3. 编译器可以模拟不对齐访问(例如,在 ia64 平台上声明为 __unaligned 的指针使用 VC++ 可以实现这一点)- 通过插入额外的检查来检测未对齐的情况,并将跨越对其边界的对象的部分分别加载/存储。然而,在本地支持不对齐访问的平台上,这甚至比不对齐访问更慢。


6
您还可以添加#4,即操作系统可以代表应用程序模拟不对齐的访问,通过捕获处理器异常并修复它来实现(类似于页面故障时发生的情况)。这比编译器在生成的代码中执行不对齐的修复要慢。Windows可以在ia64上支持此功能。 - Michael Burr
7
这个答案被引用在博客文章《数据对齐提速:神话还是现实?》中。 - Peter Mortensen
这个答案可以再具体一些。例如,数据对齐在32/64位边界上对标量操作很重要,在x86上进行SIMD操作时需要128位边界。更重要的是,你可能想指出最大的成本是操作跨越缓存行。 - awdz9nld
2
甚至有一个反例,即未对齐的数据对于将尽可能多的内容推入CPU缓存以避免缓存未命中非常有用:http://danluu.com/3c-conflict/。很遗憾那里没有评论部分,因为我想听听其他人对此的看法。希望在某个时候能够自己测试一下。当然,这取决于处理器架构。 - leetNightshade
2
@leetNightshade 这篇文章非常有趣和有用。但是我认为,它没有讨论与读取大小相关的对齐意义上的未对齐数据。它谈到了在较高地址位中发生的与缓存相关的对齐问题。为了形象化,32位地址由以下组成:TTTT TTTT TTTT TTTT TTTT SSSS SSXX XYYY,那么当前的问题只涉及Y而不涉及其他任何内容。没有人关心X和T,而您提到的非常有趣的文章只谈到了S。仅作为补充说明,S位的长度当然取决于缓存大小和缓存级别。 - Roland Pihlakas
显示剩余2条评论

6

这很大程度上取决于芯片架构。x86和POWER非常容易处理,Sparc、Itanium和VAX会抛出不同的异常。


7
确实取决于处理器。最近,我正在使用一款数字信号处理器工作,当被要求对不对齐的内存地址进行操作时,它会愉快地使用最接近的对齐内存地址进行处理。你这个邪恶的、访问不对齐内存的人,试着去调试它吧。 - Dan Moulding
事实上,为什么还要关注那些最后几位 - 真正的专家无论如何都知道自己在做什么 :) 另一方面,如果您使用忽略的位用于标记,那么在该架构上使用标记指针是一个方便的选择... - Pavel Minaev
1
@Pavel:请考虑ARM/thumb交互工作。指令的“地址”的最低有效位(lsb)指示CPU在执行该地址处的代码之前是否应进入Thumb模式(1)或ARM模式(0)。无论哪种方式,目标指令的实际字节都位于地址&~1的内存中,只是将一个值复制到程序计数器可能会切换模式并跳转。 - Steve Jessop

2

考虑以下我刚在ARM9上测试过的示例:

//Addresses       0     1     2    3     4     5     6     7      8    9
U8 u8Temp[10] = {0x11,0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00};

U32 u32Var;

u32Var = *((U32*)(u16Temp+1));  // Let's read four bytes starting from 0x22

// You would expect that here u32Var will have a value of 0x55443322 (assuming we have little endian)
// But in reallity u32Var will be 0x11443322!
// This is because we are accessing address which %4 is not 0.

4
我认为您有一个拼写错误——您的第三个声明引用了“u16Temp”变量,它在哪里声明?我只看到“u8Temp”。 - Armen Michaeli

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接