内容正文:
第9章 函数与指针
函数的运行机理
函数的嵌套
函数的递归调用
指针作为函数参数
内部函数与外部函数
静态变量
C语言程序设计
函数的运行机理
函数的调用
函数的求参顺序
函数的递归调用
指针作为函数参数
数组作为函数参数
函数的嵌套调用
01
02
04
05
03
06
07
目 录
CATALOUGUE
初心至善 匠心育人
指针数组作为函数参数
内部函数
外部函数
全局变量
外部变量
静态变量
可变参数
01
02
04
05
03
06
07
目 录
CATALOUGUE
初心至善 匠心育人
理解函数的运行机理
掌握静态变量与全局变量
熟悉函数的嵌套和递归调用
理解指针作为函数参数
学 习 目 标
LEARNING OBJECTIVES
初心至善 匠心育人
一个C语言的源程序经过编译以后形成与源程序主名相同但后缀为.exe的可执行文件,并且放在外存储器中。当该.exe可执行程序被运行时,首先从外存将程序代码加载到内存的代码区中,然后从main函数的起始处开始执行。在程序执行过程中,如果遇到对其他函数的调用,则暂停当前函数的执行,保存下一条指令的地址(即返回地址,作为从子函数返回后继续执行的入口点),并保存现场(主要是一些寄存器的内容),然后转到子函数的入口地址,执行子函数。当遇到return语句或子函数结束时,则恢复之前保存的现场,并从先前保存的返回地址开始继续执行。接下来通过一个案例来演示函数的运行机理。
9.1 函数的运行机理
初心至善 匠心育人
①指令指针的地址加1,使其指向函数调用的下一条指令(第12行语句)。该地址随后被放置在栈中,它将作为函数返回时的返回地址。
②程序跳到函数的入口地址,该地址存在于目标文件(编译生成的.obj文件)的符号表中。当调用某个函数时,就从符号表中提取该函数的地址,即该函数第1条指令的地址,然后由寄存器中的指令指针来保存。
指令指针是用于存放下一条待执行指令的地址。当第11行add函数被调用时,可以分为以下5个步骤,具体如下:
④将函数的参数,按照自右向左的方向压入栈中,本例中形式参数b先入栈,a后入栈,当所有参数都入栈后,才开始执行函数的第1条指令。局部变量c入栈,由于栈顶指针始终要指向栈顶,因此,此时栈顶指针指向c。
⑤当函数结束时,栈区中的数据要进行释放(即出栈),后进的数据先出栈,每出一个数据,栈顶指针下移,直到所有的数据都出栈。具体示例如下。
③系统根据符号表中描述的函数返回值类型在栈中开辟一块内存,该内存用来存放返回值,而此时栈顶地址被存储到栈顶指针中,从此处开始,函数返回之前的进入栈的任何数据都将被视为函数的局部变量。
9.1 函数的运行机理
初心至善 匠心育人
本例中首先弹出的是最后压入的局部变量c,接着是函数的参数a、b,最后是返回值,此时栈内只剩下一个数据,就是调用后的下一条指令的地址,该地址被加载到指令指针中,因此程序返回到函数调用后的下一条指令处来执行,并将返回值返回到该指令处。
9.1 函数的运行机理
初心至善 匠心育人
按函数在程序中出现的位置,可以将函数调用分为3种方式,具体如下:
①函数语句调用方式是指把函数调用作为一条语句,它不要求函数带回返回值,只要求函数完成一定的操作。
②函数表达式调用方式是指函数出现在一个表达式中,例如“a = 3 * add(1, 2);”,这时要求函数返回一个确定的值用来参于表达式的运算。
③函数参数调用方式是指函数调用作为函数的实参。例如“printf(“%d
”, add(1, 2));”,其中add(1, 2)是一次函数调用,它的值作为printf函数的实参。
接下来通过一个案例来演示三种方式函数的调用,具体如下例所示。
9.2 函数的调用
初心至善 匠心育人
9.2 函数的调用
第2~5行:定义了一个print函数。
第6~11行:定义了一个add函数。
第15行:通过函数语句方式调用函数。
第16行:通过函数表达式方式调用函数,add(1, 2)返回3后,执行2 * 3,结果为6,再赋值给c。
第17行:通过函数参数方式调用函数,add(1, 2)返回3后,作为printf函数的实参。
初心至善 匠心育人
在函数调用中应当注意函数求参顺序的问题。所谓求参顺序是指对实参表中各个变量是自左向右求值还是自右向左求值。大部分编译器按照自右向左的顺序来求值,只有极少数的编译器采取自左向右的顺序求值。
调用print函数之前,先要计算出实参的值,如果编译器是按照自右向左的顺序求值,则会先计算实参++a;然后计算左侧的实参a。接着程序跳转到第2行,将右侧的实参值赋值给形参b,将左侧的实参值赋值给形参a,接着打印a、b的值。如果编译器是按照自左向右的顺序求值,则会先计算左侧的实参a;然后计算右侧的实参a。从运行