我继续讲程序的流程。哪怕是高级语言编写的程序,函数”调用处理也是通过把程序计数器的值设定成函数的存储地址来实现的。不过,这和条件分支、循环的机制有所不同,因为单纯的跳转指令无法实现函数的调用。函数的调用需要在完成函数内部的处理后,处理流程再返回到函数调用点(函数调用指令的下一个地址)。
如图是给变量a和b分别代人123和456后,将其赋值给参数来调用MyFunc函数的C语言程序。图中的地址是将C语言编译成机器语言后运行时的地址。由于1行C语言程序在编译后通常会变成多行的机器语言,所以图中的地址是离散的。此外,通过跳转指令把程序计数器的值设定成0260也可实现调用MyFunc函数。函数的调用原点(0132地址)和被调用函数(0260地址)之间的数据传递,可以通过内存或寄存器来实现。不过,当函数处理进行到最后的0354地址时,我们知道应该将程序计数器的值设定成函数调用后要执行的0154地址,但实际上这一操作根本无法实现。那么,怎么办才好呢?机器语言的call指令和returm指令能够解决这个问题。建议大家把二者结合起来来记忆。函数调用使用的是call指令,而不是跳转指令。在将函数的人口地址设定到程序计数器之前,call 指令会把调用函数后①很多高级编程语言都采用类似于y-=f(x)这样的数学函数的语法来记述编写处理。我们知道,该数学函数的意思是将x这个值通过f处理后得到数值y。如果套用函数的语法,x就是参数,y就是返回值,执行函数的功能就是函数调用。
要执行的指令地址存储在名为栈”的主存内。 函数处理完毕后,再通过函数的出口来执行return 命令。return 命令的功能是把保存在栈中的地址设定到程序计数器中。如图1-7所示,MyFunc 函数被调用之前,0154地址保存在栈中。MyFunc函数的处理完毕后,栈中的0154地址就会被读取出来,然后再被设定到程序计数器中
在编译高级编程语言的程序后,函数调用的处理会转换成call指令,函数结束的处理则会转换成return指令。这样一来,程序的运行也就变得非常流畅。
接下来我们看一下CPU中出现的基址寄存器和变址寄存器。通过这两个寄存器,我们可以对主内存上特定的内存区域进行划分,从而实现类似于数组”的操作。首先,我们用十六进制数“将计算机内存上000000~ FFFFFF的地址划分出来。那么,凡是该范围的内存区域,只要有一个32位的寄存器,即可查看全部的内存地址。但如果想要像数组那样分割特定的内存区域以达到连续查看的目的,使用两个寄存器会更方便些。例如,查看0000000地址~ 000FFFF地址时
如图所示,可以将1000000存人基址寄存器,并使变址寄存器的值在0000000FFFF变化。CPU 则会把基址寄存器+变址寄存器的值解释为实际查看的内存地址。变址寄存器的值就相当于高级编程语言程序中数组的索引功能。
①数组是指同样长度的数据在内存中进行连续排列的数据构造。用一个数组名来表示全体数据,通过索引来区分数组的各个数据(元素)。有10个例如,一个10个元素的数组a,其中的各个数据就用a[0]到a[9]来表示。中括号内的数字0~9就是索引。
②二进制数的位数较多、不易理解时,通常使用十六进制数来代替二进制数。这是一种数到16就进位的计数方式。我们用AF来分别表示1015,那么,二进制数的4位( 000 FFFF)就可以用十六进制数的1位(0F)来表示。32位的二进制数,就可以用8位的十六进制数来表示。
今天的内容就先讲到这里,我们下期再聊!



