为什么ARM gcc在函数开头将r3和lr寄存器写入堆栈?

Ala*_*lan 7 c assembly gcc arm

我试着编写一个像这样的简单测试代码(main.c):

main.c
void test(){
}
void main(){
    test();
}
Run Code Online (Sandbox Code Playgroud)

然后我使用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
Run Code Online (Sandbox Code Playgroud)

汇编代码将推送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
Run Code Online (Sandbox Code Playgroud)

我的问题是为什么arm gcc选择将r3推入堆栈,甚至test()函数从不使用它?gcc只是随机选择1个寄存器来推送吗?如果它是堆栈对齐(ARM的8个字节)要求,为什么不只是减去sp?谢谢.

==================更新==========================

@KemyLand为了您的答案,我有另一个例子:源代码是:

void test1(){
}
void test(int i){
        test1();
}
void main(){
        test(1);
}
Run Code Online (Sandbox Code Playgroud)

我使用上面相同的编译命令,然后获得以下程序集:

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
Run Code Online (Sandbox Code Playgroud)

如果第一个例子中的push {r3,lr}是使用较少的指令,为什么在这个函数test()中,编译器不只是使用一条指令?

push {r0, lr}
Run Code Online (Sandbox Code Playgroud)

它使用3条指令代替1条指令.

push {lr}
sub sp, sp #12
str r0, [sp, #4]
Run Code Online (Sandbox Code Playgroud)

顺便说一句,为什么它sub为12,堆栈是8字节对齐,它可以只用4对吧?

344*_*442 7

根据标准ARM嵌入式ABI,r0through r3用于将参数传递给函数,并且其返回值同时lr(aka :) r14是链接寄存器,其目的是保存函数的返回地址.

显然lr必须保存,否则main()将无法返回其调用者.

现在众所周知的是,每个ARM指令都需要32位,如前所述,ARM的调用堆栈对齐要求为8个字节.而且,作为奖励,我们使用嵌入式 ARM ABI,因此应优化代码大小.因此,lr通过推送未使用的寄存器来保存和对齐堆栈(r3不需要,因为test()不接受参数也不返回任何内容),然后使用单个32位指令弹出单个32位指令更有效而不是添加更多指令(因而浪费宝贵的内存!)来操纵堆栈指针.

毕竟,结论这只是GCC的优化是非常合乎逻辑的.