3.6.3. 举例分析C语言函数调用是如何使用堆栈的

对于上面的解释的堆栈的作用显得有些抽象,此处再用例子来简单说明一下,就容易明白了:

用:

arm-inux-objdump –d u-boot > dump_u-boot.txt

可以得到dump_u-boot.txt文件。该文件就是中,包含了u-boot中的程序的可执行的汇编代码,其中我们可以看到C语言的函数的源代码,到底对应着那些汇编代码。

下面贴出两个函数的汇编代码,

一个是clock_init,另一个是与clock_init在同一C源文件中的,另外一个函数CopyCode2Ram

            
33d0091c <CopyCode2Ram>:
33d0091c:	e92d4070 	push	{r4, r5, r6, lr}1
33d00920:	e1a06000 	mov	r6, r0
33d00924:	e1a05001 	mov	r5, r1
33d00928:	e1a04002 	mov	r4, r2
33d0092c:	ebffffef 	bl	33d008f0 <bBootFrmNORFlash>2
... ...
33d00984:	ebffff14 	bl	33d005dc <nand_read_ll>
... ...
33d009a8:	e3a00000 	mov	r0, #0	; 0x03
33d009ac:	e8bd8070 	pop	{r4, r5, r6, pc}4

33d009b0 <clock_init>:
33d009b0:	e3a02313 	mov	r2, #1275068416	; 0x4c0000005
33d009b4:	e3a03005 	mov	r3, #5	; 0x5
33d009b8:	e5823014 	str	r3, [r2, #20]
... ...
33d009f8:	e1a0f00e 	mov	pc, lr6
            

1

此处就是我们所期望的,用push指令,保存了r4,r5,r以及lr。

用push去保存r4,r5,r6,那是因为所谓的保存现场,以后后续函数返回时候再恢复现场,

2

上述用push去保存lr,那是因为函数CopyCode2Ram里面在此处调用了bBootFrmNORFlash

以及也调用了nand_read_ll:

33d00984:	ebffff14 	bl	33d005dc <nand_read_ll>

也用到了bl指令,会改变我们最开始进入clock_init时候的lr的值,所以我们要用push也暂时保存起来。

3

把0赋值给r0寄存器,这个就是我们所谓返回值的传递,是通过r0寄存器的。

此处的返回值是0,也对应着C语言的源码中的“return 0”.

4

把之前push的值,给pop出来,还给对应的寄存器,其中最后一个是将开始push的lr的值,pop出来给赋给PC,因为实现了函数的返回。

5

可以看到此处是该函数第一行

其中没有我们所期望的push指令,没有去将一些寄存器的值放到堆栈中。这是因为,我们clock_init这部分的内容,所用到的r2,r3等寄存器,和前面调用clock_init之前所用到的寄存器r0,没有冲突,所以此处可以不用push去保存这类寄存器的值,不过有个寄存器要注意,那就是r14,即lr,其是在前面调用clock_init的时候,用的是bl指令,所以会自动把跳转时候的pc的值赋值给lr,所以也不需要push指令去将PC的值保存到堆栈中。

6

而此处是clock_init的代码的最后一行

就是我们常见的mov pc, lr,把lr的值,即之前保存的函数调用时候的PC值,赋值给现在的PC,这样就实现了函数的正确的返回,即返回到了函数调用时候下一个指令的位置。

这样CPU就可以继续执行原先函数内剩下那部分的代码了。

[提示]对于使用哪个寄存器来传递返回值

当然你也可以用其他暂时空闲没有用到的寄存器来传递返回值,但是这些处理方式,本身是根据ARM的APCS的寄存器的使用的约定而设计的,你最好不要随便改变使用方式,最好还是按照其约定的来处理,这样程序更加符合规范。