如何避免在cortex M4上使用float来避免未对齐的访问异常 C不是可移植的汇编语言,它有自己的规则

问题描述

我在某些代码中遇到了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变量也不安全时,避免这种问题的正确策略是什么?

解决方法

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

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

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

在您的情况下,如果*(uint32_t *)src未对齐,则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 type属性用于任何类型,包括float,如果您需要unaligned_float。)

如果您还希望使用别名安全的__attribute__((may_alias,aligned(1))),则

uint32_t可能很有用。 (许多嵌入式构建都是使用-fno-strict-aliasing编译的,其中每种类型都隐式地为may_alias,但是如果在所有实际需要的地方都严格使用此代码,则可以使代码严格别名是安全的。)

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...