请考虑以下代码:
extern unsigned int foo(char c, char **p, unsigned int *n);
unsigned int test(const char *s, char **p, unsigned int *n)
{
unsigned int done = 0;
while (*s)
done += foo(*s++, p, n);
return done;
}
汇编中的输出:
00000000 <test>:
0: b5f8 push {r3, r4, r5, r6, r7, lr}
2: 0005 movs r5, r0
4: 000e movs r6, r1
6: 0017 movs r7, r2
8: 2400 movs r4, #0
a: 7828 ldrb r0, [r5, #0]
c: 2800 cmp r0, #0
e: d101 bne.n 14 <test+0x14>
10: 0020 movs r0, r4
12: bdf8 pop {r3, r4, r5, r6, r7, pc}
14: 003a movs r2, r7
16: 0031 movs r1, r6
18: f7ff fffe bl 0 <foo>
1c: 3501 adds r5, #1
1e: 1824 adds r4, r4, r0
20: e7f3 b.n a <test+0xa>
使用 arm-none-eabi-gcc 版本编译的C代码:4.9.1,5.4.0,6.3.0和7.1.0 on Linux主机。所有GCC版本的装配输出都相同。
CFLAGS:= -Os -march = armv6-m -mcpu = cortex-m0plus -mthumb
我对执行流程的理解如下:
我自己的大会。没有经过广泛测试,但我绝对不需要将R4 + LR推到堆栈上:
编辑:根据提供的答案,我的下面的示例将失败,因为R1和R2没有通过调用foo来持久()
51 unsigned int __attribute__((naked)) test_asm(const char *s, char **p, unsigned int *n)
52 {
53 // r0 - *s (move ptr to r3 and dereference it to r0)
54 // r1 - **p
55 // r2 - *n
56 asm volatile(
57 " push {r4, lr} \n\t"
58 " movs r4, #0 \n\t"
59 " movs r3, r0 \n\t"
60 "1: \n\t"
61 " ldrb r0, [r3, #0] \n\t"
62 " cmp r0, #0 \n\t"
63 " beq 2f \n\t"
64 " bl foo \n\t"
65 " add r4, r4, r0 \n\t"
66 " add r3, #1 \n\t"
67 " b 1b \n\t"
68 "2: \n\t"
69 " movs r0, r4 \n\t"
70 " pop {r4, pc} \n\t"
71 );
72 }
问题:
为什么GCC会为这么重要的功能存储这么多寄存器?
为什么在用ABI写入R0-R3为参数寄存器时推送R3 并且应该是调用者保存,应该在被调用的函数内安全使用 在这种情况下test()
为什么它将R1复制到R6和R2复制到R7,而extern函数的原型差不多 理想情况下匹配test()函数。所以R1和R2已经准备好通过了 到foo()例程。我的理解是,之前只需要取消引用R0 致电foo()
答案 0 :(得分:2)
由于test
不是叶子函数,因此必须保存LR。函数使用r5-r7
来存储跨函数调用使用的值,因为它们不是临时的,所以必须保存它们。推动r3
以对齐堆栈。
向push
添加额外的寄存器是一种快速而紧凑的方式来对齐堆栈。
r1
和r2
可能会被foo
的调用破坏,因为在调用之后需要最初存储在这些寄存器中的值,它们必须存储在某个位置幸存的电话。