本节书摘来自华章计算机《计算机系统:核心概念及软硬件实现(原书第4版)》一书中的第2章,第2.3节,作者:[美] J. 斯坦利·沃法德(J. Stanley Warford)著, 更多章节内容可以访问云栖社区“华章计算机”公众号查看。
C++有两种函数:一种返回值为空,另一种返回值为非空类型。函数main()返回整型,不是空值。操作系统根据这个整数来确定程序是否正常结束。返回值为空的函数(简称空函数)完成处理,不返回任何值。返回值为空的函数的常见用法是输入或输出一组值。2.3.1空函数和传值调用的参数图2-16中的程序使用空函数打印一个数值的柱状图。程序把第一个值读入整型变量numPts。在主程序中,全局变量j控制for循环执行numPts次。每次执行循环都调用空函数printbar。图2-17展示了图2-16中程序开始执行时的轨迹。当调用一个空函数时,运行时栈中的分配按照以下顺序进行:压入实际参数(简称实参)。压入返回地址。压入局部变量的存储空间。图2-17e是图2-16所示程序分配过程的开始,程序压入形式参数(简称形参)n的值value。在图2-17f中,压入返回地址。图2-17g中压入局部变量的存储空间。分配过程完成后,列表中的最后一个局部变量k在栈的顶部。被压入运行时栈的所有项目的集合称为栈帧(stack frame)或活跃记录(activation record)。在图2-16的程序中,该空函数的栈帧由3项组成:n、返回地址和k。图中用ra1标识的返回地址是主程序中for语句结束处的地址。main函数的栈帧由两项组成:返回值和返回地址。空函数打印柱状图的一根柱子后,控制返回到主程序。运行时栈上的项目按照与分配顺序相反的顺序进行释放。过程如下:释放局部变量的存储空间。弹出返回地址。释放实参。
程序通过返回地址知道,在执行完空函数的最后一条语句后,接下来去执行主程序中的哪条语句。在主程序代码中,那条语句被标识为ra1,是过程调用后面的一条语句。2.3.2函数的例子图2-18中的程序用函数计算一个整数的阶乘值。程序提示用户输入一个小的整数,把它作为参数传递给函数fact。图2-19展示了图2-18中函数的分配过程,该函数返回实参的阶乘。图2-19c表明首先压入返回值的存储空间。图2-19d展示压入形参n的值num。图2-19e中压入返回地址。图2-19f和g压入局部变量f和j的存储空间。这个函数的栈帧有5项。图中标记为ra1的返回地址表示主程序中cout语句的地址。控制从该函数返回到调用该函数的语句(简称调用语句)。这与空函数是不同的,在空函数的调用中,控制是返回调用语句后面的那条语句。
2.3.3传引用调用的参数前面讲述程序中的过程和函数都是通过值传递参数的。在传值调用中,形参获得实参的值。如果被调用的过程改变了它的形参值,调用过程中相应的实参值不变。被调用过程所做的任何改变都是对运行时栈上的值进行的,当栈帧释放后,任何被改变的值也随之释放。如果一个过程意在改变调用程序中实参的值,那么就要使用传引用调用而不是传值调用。在传引用调用中,形参获得的是对实参的一个引用。如果被调用过程改变了它的形参的值,那么调用程序中相应的实参也会改变。要指定一个参数为传引用调用,就要在参数列表中该参数的类型后加一个&符号。如果没有这个&符号,编译器会假定该参数是传值调用的。图2-20中的程序用传引用调用改变实参的值。它提示用户输入两个整数,排序输入。它有一个空函数order,调用另一个空函数swap。图2-21展示了整个程序的分配和释放的顺序。
图2-21c中order的栈帧有3项。形参x和y是传引用调用的,有箭头从运行时栈上的x指向主程序中的a,这表明x引用a。类似地,从运行时栈上的y指向主程序中b的箭头表明y引用b。ra1标识的返回地址是cout语句的地址,该语句在主函数中对order的调用之后。图2-21d中swap的栈帧有4项。r引用x,x引用a,因此r引用a。箭头从运行时栈上的r指到a,箭头同样从x指向a。类似地,箭头从运行时栈上的s指向b,箭头同样从y指向b。ra2标识的返回地址是函数order最后一条语句的地址。swap中的语句交换s和r的值。因为r引用a,s引用b,所以它们交换的是主程序中a和b的值。当空函数结束时,要释放它的栈帧,栈帧中的返回地址告诉计算机接下来去执行哪条指令。图2-21e展示了从空函数swap返回的过程,释放其栈帧。swap栈帧中的返回地址告诉计算机在释放该栈帧之后顺序执行标号为ra2的语句。虽然图2-20中的代码ra2处并没有语句,但是空函数结尾处隐含有一个return语句,这在HOL6层是不可见的。图2-21f展示了释放order栈帧的过程。order栈帧中的返回地址告诉计算机在释放该栈帧之后,执行主程序中的cout语句。
因为栈是LIFO结构,所以在函数结束时,最后一个被压入运行时栈的栈帧将第一个被弹出。因此返回地址将把控制返回到最近的调用函数。运行时栈的LIFO属性将是理解2.4节中递归的基础。你可能已经注意到了,main()是一个总是返回整数的函数,截至目前,所有的程序都向操作系统返回0。此外,截至目前,所有的主程序函数都没有参数。虽然主程序有参数是很常见的,但是本书中的主程序都没有。为了保持图的简单,后面的图中都将省略主程序的relVal和retAddr。实际的C++编译器必须处理这两者。
相关资源:敏捷开发V1.0.pptx