如何避免在 cortex M4 上使用 float 的未对齐访问异常

how to avoid unaligned access exceptions with float on cortex M4

我在某些计算带有整数操作数的浮点表达式的代码中遇到 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 例如在堆栈上分配了一个整数。

所以我的问题是:

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

When the operand is not 32-bits aligned (which is not under my control)

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/ 有关 target-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-compliant uint32_t 对象,并且必须发出以即使没有对齐也能工作的方式加载的代码。这可能非常低效;我没有检查 GCC 的 asm 输出。

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

如果您还想要

__attribute__((may_alias, aligned(1)))__attribute__((may_alias, aligned(1))) 可能会很有用 aliasing-safe uint32_t。 (许多嵌入式构建使用 -fno-strict-aliasing 编译,其中每种类型都是隐式的 may_alias,但如果您在实际需要它的任何地方严格使用它,您可以使您的代码 strict-aliasing 安全。)