我试着编写一个像这样的简单测试代码(main.c):
main.c
void test(){
}
void main(){
test();
}
然后我使用arm-non-eabi-gcc编译和objdump来获取汇编代码:
arm-none-eabi-gcc -g -fno-defer-pop -fomit-frame-pointer -c main.c
arm-none-eabi-objdump -S main.o > output
汇编代码将推送r3和lr寄存器,即使函数什么都没做。
main.o: file format elf32-littlearm
Disassembly of section .text:
00000000 <test>:
void test(){
}
0: e12fff1e bx lr
00000004 <main>:
void main(){
4: e92d4008 push {r3, lr}
test();
8: ebfffffe bl 0 <test>
}
c: e8bd4008 pop {r3, lr}
10: e12fff1e bx lr
我的问题是为什么arm gcc选择将r3推入堆栈,甚至test()函数从不使用它? gcc只是随机选择1个寄存器来推送吗? 如果堆栈对齐(ARM的8个字节)要求,为什么不只是减去sp?感谢。
==================更新==========================
@KemyLand对于你的回答,我有另一个例子: 源代码是:
void test1(){
}
void test(int i){
test1();
}
void main(){
test(1);
}
我使用上面相同的编译命令,然后获得以下程序集:
main.o: file format elf32-littlearm
Disassembly of section .text:
00000000 <test1>:
void test1(){
}
0: e12fff1e bx lr
00000004 <test>:
void test(int i){
4: e52de004 push {lr} ; (str lr, [sp, #-4]!)
8: e24dd00c sub sp, sp, #12
c: e58d0004 str r0, [sp, #4]
test1();
10: ebfffffe bl 0 <test1>
}
14: e28dd00c add sp, sp, #12
18: e49de004 pop {lr} ; (ldr lr, [sp], #4)
1c: e12fff1e bx lr
00000020 <main>:
void main(){
20: e92d4008 push {r3, lr}
test(1);
24: e3a00001 mov r0, #1
28: ebfffffe bl 4 <test>
}
2c: e8bd4008 pop {r3, lr}
30: e12fff1e bx lr
如果第一个例子中的push {r3,lr}是使用较少的指令,为什么在这个函数test()中,编译器不只是使用一条指令?
push {r0, lr}
它使用3条指令而不是1条。
push {lr}
sub sp, sp #12
str r0, [sp, #4]
顺便说一句,为什么它以12为子进行sub sp,堆栈是8字节对齐的,它可以只用4对子进行分配?
答案 0 :(得分:6)
根据Standard ARM Embedded ABI,r0
到r3
用于将参数传递给函数及其返回值,同时lr
(aka:{{ 1}})是链接寄存器,其目的是保存函数的返回地址。
很明显,r14
必须保存,否则lr
将无法返回其来电者。
现在众所周知,每条ARM指令需要32位,正如您所提到的,ARM的调用堆栈对齐要求为8字节。而且,作为奖励,我们使用嵌入式 ARM ABI,因此应优化代码大小。因此,使用单个32位指令同时保存main()
并通过按下未使用的寄存器(lr
来对齐堆栈,因为r3
确实更有效率不接受参数也不返回任何东西),然后弹出一个32位指令,而不是添加更多指令(因而浪费宝贵的内存!)来操纵堆栈指针。
毕竟,结论这只是GCC的优化,这是非常符合逻辑的。