如何避免在皮质 M4 上浮动的未对齐访问异常

0 c assembly gcc arm memory-alignment

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

我试图在这里重现 Godbolt 上的行为,生成的代码与我在设备上得到的一致。

基本上,下面的反汇编代码

vldr.32 s0, [r0]  @ int
Run Code Online (Sandbox Code Playgroud)

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

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

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

uint32_t pippo =  *(uint32_t *)src;

float pippof = pippo * 10.0f;
Run Code Online (Sandbox Code Playgroud)

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

所以我的问题是:

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

Pet*_*des 5

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

当操作数不是 32 位对齐时(这不在我的控制之下)

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

特别*(uint32_t *)src是在您的情况下,如果src未对齐,则是未定义的行为。这就是为什么允许为以后使用该数据而生成的代码假定它对齐的。ARM 程序集恰好可以处理未对齐的整数加载这一事实与任何事情无关,除了为什么它碰巧在禁用优化的情况下工作。请参阅https://trust-in-soft.com/blog/2020/04/06/gcc-always-assumes-aligned-pointers/为什么对 mmap'ed 内存的未对齐访问有时会在 AMD64 上出现段错误?有关目标 ISA 未对齐行为/保证在 C 中不使其安全的更多示例和讨论。


如果您的数据比这少对齐,则需要某种方法来进行安全的未对齐加载。一种 ISO-C 标准方式是使用memcpy. (GCC 将可靠地将它内联到它知道如何执行未对齐整数加载的目标上,例如具有足够新的 ARM-march=-mcpu=。除非您使用过-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。)

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