如何强制ARM64 GCC用指令构造双精度浮点数,规避.rodata段
强制AArch64 GCC通过指令构造浮点常量,避免使用数据段
问题说明
ARM64指令长度为32位,使用64位浮点常量时,编译器通常会将字面量存入数据段,再通过加载指令读取。但AArch64 GCC在部分场景下会通过mov/movk/fmov等指令直接构造双精度浮点数,无需依赖数据段。例如:
- 初始化值为
{5.5, 6.6}时,编译器会生成指令构造常量 - 换成
{1.1, 2.2}时,编译器默认将常量存入数据段,通过adrp+ldr加载
需要找到编译器选项强制开启指令构造浮点常量的特性,避免嵌入式代码中引入数据段。
示例汇编对比
常量存入数据段({1.1, 2.2})
000000000044fda0 <foo>: 1.1, 2.2 44fda0: d2800000 mov x0, #0x0 // #0 44fda4: b0000002 adrp x2, 450000 <pmalloc+0x78> // 计算数据段地址 44fda8: b0000001 adrp x1, 450000 <pmalloc+0x78> 44fdac: fd472c41 ldr d1, [x2, #3672] // 加载双精度常量 44fdb0: fd473020 ldr d0, [x1, #3680] 44fdb4: 6d000001 stp d1, d0, [x0] ... 44fdbc: d65f03c0 ret
指令构造常量({5.5, 6.6})
000000000044fda0 <foo>: 5.5, 6.6 44fda0: d2800000 mov x0, #0x0 // #0 44fda4: b203e7e1 mov x1, #0x6666666666666666 // 加载低64位立即数 44fda8: 1e62d001 fmov d1, #5.500000000000000000e+00 44fdac: f2e80341 movk x1, #0x401a, lsl #48 // 拼接高16位 44fdb0: 9e670020 fmov d0, x1 // 转成浮点寄存器 44fdb4: 6d000001 stp d1, d0, [x0] ... 44fdbc: d65f03c0 ret
环境信息
使用的编译器版本:
$ aarch64-linux-gnu-gcc --version aarch64-linux-gnu-gcc (Ubuntu/Linaro 7.5.0-3ubuntu1~18.04) 7.5.0 Copyright (C) 2017 Free Software Foundation, Inc.
测试用C代码(float.c):
void foo(void* base_ptr) { double buffer[2]= {5.5, 6.6}; double* q = (double*)base_ptr; *q++ = buffer[0]; *q = buffer[1]; return ; } int main() { double f[2]; foo(f); }
解决方案
使用-mno-fp-read-only-data编译选项,强制GCC通过指令构造所有双精度浮点常量,而非将其存入只读数据段:
aarch64-linux-gnu-gcc -mno-fp-read-only-data float.c -o float
该选项会让GCC针对所有浮点常量生成mov/movk/fmov序列,即使需要多条指令,也不会将常量写入数据段,完全符合嵌入式场景下避免数据段的需求。
注:如果允许浮点常量的近似值,可以配合
-ffast-math进一步优化指令数量,但会牺牲数值精度;若需要精确值,仅使用-mno-fp-read-only-data即可。
内容的提问来源于stack exchange,提问作者Capital S
相关产品推荐
相关产品推荐

