1.5 函数的调用机制
接下来,我们继续介绍程序的流程。哪怕是高级语言编写的程序,函数调用处理也是通过把程序计数器的值设定成函数的存储地址来实现的。不过,这和条件分支、循环的机制有所不同,因为单纯的跳转指令无法实现函数的调用。函数的调用需要在完成函数内部的处理后,处理流程再返回到函数调用点(函数调用指令的下一个地址)。因此,如果只是跳转到函数的入口地址,处理流程就不知道应该返回至哪里了。
图1-7是给变量a和b分别代入123和456后,将其赋值给参数(parameter)来调用MyFunc函数的C语言程序。图中的地址是将C语言编译成机器语言后运行时的地址。由于1行C语言程序在编译后通常会变成多行的机器语言,所以图中的地址是离散的。
图1-7 程序调用函数示例(这里直接展示了C语言的源代码,实际上各地址存储的应该是变换成机器语言后的程序)
此外,通过跳转指令把程序计数器的值设定成0260也可实现调用MyFunc函数。函数的调用原点(0132地址)和被调用函数(0260地址)之间的数据传递,可以通过内存或寄存器来实现。不过,当函数处理进行到最后的0354地址时,我们知道应该将程序计数器的值设定成函数调用后要执行的0154地址,但实际上这一操作根本无法实现。那么,怎么办才好呢?
机器语言的call指令和return指令能够解决这个问题。建议大家把二者结合起来来记忆。函数调用使用的是call指令,而不是跳转指令。在将函数的入口地址设定到程序计数器之前,call指令会把调用函数后要执行的指令地址存储在名为栈的主存内。函数处理完毕后,再通过函数的出口来执行return命令。return命令的功能是把保存在栈中的地址设定到程序计数器中。如图1-7所示,MyFunc函数被调用之前,0154地址保存在栈中。MyFunc函数的处理完毕后,栈中的0154地址就会被读取出来,然后再被设定到程序计数器中(图1-8)。
图1-8 函数调用中程序计数器和栈的职能
在编译高级编程语言的程序后,函数调用的处理会转换成call指令,函数结束的处理则会转换成return指令。这样一来,程序的运行也就变得非常流畅。