【问题标题】:how to avoid unaligned access exceptions with float on cortex M4如何在皮质 M4 上使用浮点数避免未对齐的访问异常
【发布时间】:2020-12-29 05:08:21
【问题描述】:

我在一些计算带有整数操作数的浮点表达式的代码中遇到了 HardFault 异常。 操作数通过地址传递,然后转换(隐式或显式转换)为浮点数。 当操作数不是 32 位对齐时(不在我的控制范围内),我得到了异常。

我尝试在 Godbolt here 上重现该行为,生成的代码与我在设备上得到的一致。

基本上如下反汇编代码

vldr.32 s0, [r0]  @ int

在需要对齐地址的vldr指令中直接使用传递给函数的可能未对齐的地址。

我发现this question 解决了类似的问题,但他们在那里谈论浮点指针。在那种情况下,我知道浮动不能不对齐。

在我的例子中,我处理的是允许不对齐的整数,但是编译器假定它仍然可以使用 vldr 指令中的地址。更让我疑惑的是这段代码

uint32_t pippo =  *(uint32_t *)src;

float pippof = pippo * 10.0f;

在提供未对齐地址时可能会或可能不会产生异常,具体取决于优化级别,因为例如在堆栈上分配了一个整数 -O0

所以我的问题是:

  • 对于编译器(或后端,也许)来说这是正确的行为吗?由于整数可以不对齐,我希望生成的代码从 CPU 寄存器传递。
  • 什么是避免此类问题的正确策略,即使通过临时 int 变量也不安全?

【问题讨论】:

  • 如果 src 被定义为 32 位变量类型以外的东西,那么当然这可能会出错,这并不奇怪,这不是正确/安全的方式来获取例如字节数组和把它们变成一个词。
  • 如果您要求编译器做一些可以/将在特定目标上产生错误的事情,那么是的,它可以/将产生错误,而不是编译器问题。
  • 策略是编写更安全的代码。

标签: c assembly gcc arm memory-alignment


【解决方案1】:

C 不是可移植的汇编语言,它有自己的规则

当操作数不是 32 位对齐时(不在我的控制范围内)

alignof(uint32_t) 为 4,因此允许编译器假定 4 字节对齐。取消引用不是 4 字节对齐的 uint32_t* 是 C 未定义的行为,所以是的,编译器 100% 允许假设这不会发生。

具体而言,如果 src 未对齐,*(uint32_t *)src 是未定义的行为。这就是为什么允许为以后使用该数据而生成的代码假定它对齐的。 ARM 程序集恰好可以处理未对齐的整数加载这一事实与 没有 有任何关系,除了它碰巧在禁用优化的情况下工作的原因。请参阅https://trust-in-soft.com/blog/2020/04/06/gcc-always-assumes-aligned-pointers/Why does unaligned access to mmap'ed memory sometimes segfault on AMD64? 获取更多关于目标 ISA 错位行为/保证不会使其在 C 中安全的示例和讨论。


如果您有比这更少对齐的数据,您需要一些方法来进行安全的未对齐加载。一种 ISO-C 标准方式是使用memcpy。 (GCC 会可靠地将它内联到它知道如何执行未对齐整数加载的目标上,例如带有足够新的-march=-mcpu= 的ARM。除非你使用过-fno-builtin-memcpy 或类似的东西,在这种情况下,这将是开销太大的错误选择。)

另一种方法是使用 GNU C typedef,例如
typedef uint32_t unaligned_u32 __attribute__((aligned(1))) 并使用unaligned_u32*

这让编译器知道它不是一个普通的 ABI 兼容 uint32_t 对象,并且必须发出以即使没有对齐也能工作的方式加载的代码。这可能效率很低;我没有检查 GCC 的 asm 输出。

(您可以将此 GNU C 类型属性用于任何类型,包括 float,如果您想要 unaligned_float。)

如果您还想要一个别名安全的uint32_t__attribute__((may_alias, aligned(1))) 会很有用。 (许多嵌入式构建使用 -fno-strict-aliasing 进行编译,其中每种类型都隐含为 may_alias,但如果您在实际需要的任何地方都严格使用它,您可以使您的代码使用严格别名安全。)

【讨论】:

  • 使用memcpy 似乎做了正确有效的事情:它使用ldr 加载到整数寄存器中,未对齐访问是可以的,然后将其移动到适当的浮点寄存器中.见godbolt.org/z/Wexs8v。对于 OP:现代编译器通常会优化常量大小的 memcpy,就像没有对齐假设的赋值一样,所以即使表面上看起来它可能是一个昂贵的函数调用,但实际上并非如此。
  • @NateEldredge:谢谢。在某些 ARM CPU 上,vector -> integer regster 可能会很慢。我不知道整数 -> 向量是否也可以在某些仅松散耦合的有序 CPU 上停止整数或向量管道,或者这通常没问题。
  • @old_timer:我的回答是假设 GCC 将 inline memcpy 使用对已知目标 ISA 有效的方法来处理单个可能未对齐的单词。您实际上并不想让编译器将bl memcpy 发送到对齐的内存目标!那将是垃圾。如果您使用-ffreestanding-fno-builtin-memcpy 或其他东西进行编译,请务必使用带有__attribute__((aligned(1))) 的typedef 来让GCC 内联代码。
  • 回应的是 cmets 而不是答案。
  • @old_timer:如果您要回复 Nate,请使用 @Nate ping 他,并告诉我您要回复的是谁。无论如何,我更新了我的答案,以确保我的意思是明确的;这得到了很大的支持,所以我想说明更多的假设并理解我的答案所依据的。
猜你喜欢
  • 1970-01-01
  • 2020-11-17
  • 1970-01-01
  • 1970-01-01
  • 2013-09-06
  • 2018-04-23
  • 1970-01-01
  • 1970-01-01
  • 2012-03-19
相关资源
最近更新 更多