C++ 中未对齐访问的正确性

Correctness of misaligned access in C++

据我所知,未对齐的访问主要意味着两件事:

  1. 可能性能下降
  2. 失去对齐访问所具有的加载和存储的原子性

假设性能不是问题并且我希望从软件中获得正确性,那么未对齐的访问有多糟糕?我的理解是 x86 CPU 将正确处理此类访问,但可能需要做额外的工作才能获取数据。

让我问这个问题的原因是用 -fsanitize=undefined 编译我的代码。我收到 许多 个关于未对齐 stores/loads 的错误。我不确定这是否是一个问题,因为:

  1. 存储只在单线程进程的数据准备期间执行,所以我不担心原子性的损失
  2. 加载是在多线程进程中执行的,其中许多线程(四个或更多)访问数据,但数据未被任何线程修改(保存在 const uint8_t* 变量中)

访问未对齐的原因是 const uint8_t* 数组包含来自许多不同类型的字节(uint8_tuint16_tuint32_tuint64_t , 和 int64_t).

我确信没有负载超出分配的 uint8_t 数组的范围(例如,程序从不从指向最后一个、两个或三个字节的地址加载 uint64_t分配的内存块),并确保我的访问都是 正确的 - 只是未对齐。

我读到的另一件事是,这样的加载可能违反了严格的别名规则,但是代码编译时没有一个警告 -Wstrict-aliasing -Werror(我很久以前就启用了)。

我应该填充 uint8_t 数组中的数据以确保访问对齐,还是我可以安全地忽略警告?

有些平台不支持未对齐的访问(您会遇到崩溃)。并且,有些平台支持未对齐访问,但有些 asm 指令需要对齐访问。比如ARM上有LDRD指令,需要对齐内存地址。不幸的是,编译器可以随意使用这条指令。但是,通常,有一个编译器扩展告诉编译器指针未对齐,因此它不会使用 LDRD。

在支持UA的平台上,有你说的惩罚。

我推荐你使用memcpy。它适用于所有平台,现在编译器可以很好地优化它(所以你不会得到 memcpy 调用,而是快速的 mov 指令)。

主要问题不是性能或原子性,而是正确性。根据 C 和 C++ 标准,未对齐的访问会调用未定义的行为,因此您不能依赖任何特定的结果。它可能会工作,也可能会崩溃。或者它可能先工作,然后在稍后停止工作。这是您收到的错误消息的本质。如果您知道错误总是对您有用,您可以选择忽略这些错误,但是由于您要求标记此类错误,通过使用相应的编译器开关,您应该努力避免它们是合理的,尤其是如果你不能绝对确定你的代码会永远留在这个平台上。此外,您怎么知道即使在同一平台上它也能始终为您工作?

看你写的,数据好像是同一台机器写的,后面读的,只是线程不同而已。如果是这样,您应该尝试以正确对齐的方式写入数据,即在适当的地方使用填充。通过将数据打包到正确定义的结构而不是非结构化缓冲区中,您可能能够获得编译器的帮助。这也会给你更多的类型安全。

否则你需要担心的不仅仅是对齐问题。例如,您还需要考虑字节顺序。在这种情况下,您可能正在编写一种可能最终位于不同机器上的外部数据记录。您正在寻找一种机器中立的外部数据表示,您可以自己定义它,或者您最好使用为 RPC 发明的几种标准表示之一,它的优点是您可以找到库来进行读写。