ARM GCC 为何在微小函数中压入并弹出临时寄存器 r3?
数据阿明
结论
在这段由 ARM GCC 生成的函数序言中,r3 用来补足栈空间,满足函数调用时的栈对齐要求。my_function 还需要先保存 lr,因为执行 bl my_sub_function 会用新的返回地址覆盖 lr。
为什么需要这两个寄存器
按照 ARM 的过程调用约定,函数调用边界处的栈指针需要保持 8 字节对齐。进入函数时,栈通常已经按 8 字节对齐。如果只保存 4 字节的 lr,栈指针就会偏移 4 字节,调用 my_sub_function 时便不再满足对齐要求。
这段代码一次压入两个 32 位寄存器,共占 8 字节:
push {r3, lr} @ 保存 lr,并额外使用 4 字节保持栈对齐
bl my_sub_function
pop {r3, pc} @ 恢复栈指针;同时通过 pc 返回
r3 是易失寄存器。按照调用约定,调用者不能指望被调用函数保留它的值。这里保存 r3 是为了补足 4 字节的栈空间,让总共压入的寄存器占 8 字节。弹出时,r3 的原值也随之恢复;ABI 并不要求保留这个值。
pop {r3, pc} 的作用
pop 从栈中取回两个寄存器:原来的 r3,以及此前保存在栈上的返回地址。将返回地址弹入 pc,函数就能直接返回调用者,因此这里用 pc 接收返回地址:
pop {r3, pc}
注意事项
以上解释针对所示汇编。编译器也可以用其他指令组合调整栈空间,具体生成形式会受目标架构、编译器版本和选项影响。调用 my_sub_function 时,栈需要保持所需的对齐,lr 也需要事先保存。
备注:内容仅供参考。
