C函数中形参传递方式的差别

C函数中的形式参数可以是基本类型变量名,构造类型变量名和指针类型变量名。对于不同的形式参数,其传递参数的方式不同,总体来说分成两种:按值传递和按地址传递。

当形参是基本类型变量名时,采用按值传递方式;当形参是指针类型变量名或者构造类型变量名时,采用按地址传递方式。

下面通过两个相似的程序说明二者的区别:

可以看出,程序一实现了a和b的值的交换,而程序二并没有实现。

先看一下两个程序的栈帧内容有何差别:

可以看出他们仅在压入栈中的参数不同。程序一中,main函数把变量a和b的地址作为实参压入了栈中;程序二中,则把变量a和b的值作为实参压入了栈中。

看一下两段程序的汇编代码(AT&T格式):

对比图:

在给swap过程传递参数时,程序一用了leal指令,而程序二用的是movl指令。因此程序一传递的是a和b的地址,而程序二传递的是a和b的内容。

程序一的swap过程比程序二的swap过程多了三条指令。而且,由于程序一的swap过程更复杂,使用了较多的寄存器,除了三个调用者保存寄存器外,还使用了被调用者保存寄存器EBX,它的值必须在准备阶段被保存到栈中,而在结束阶段从栈中恢复。因而它比程序二又多了一条push指令和一条pop指令。

再来看一下执行swap过程后的main的栈帧中的状态:

程序一的swap函数的形式参数x和y用的是指针型变量名,相当于间接寻址,需要先取出地址,然后根据地质再存取x和y的值,因而改变了调用过程main的栈帧中局部变量a和b所在位置的内容。

而程序二中的swap函数的形式参数x和y用的是基本数据类型变量名,直接存取x和y的内容,因而改变的是swap函数的入口参数x和y所在位置的值。

程序一和程序二有这么明显的差别,这些差别造成最终结果的不同是重要的。这个不同就是,程序一中调用swap后回到main执行时,a和b的值已经交换过了,而在程序二时,swap的过程实际上交换的是其两个入口参数所在位置上的内容,并没有真正交换a和b的值。

 

从上面的例子我们可以看出,编译器并不为形式参数分配存储空间,而是给形式参数对应的实参分配空间,形式参数实际上只是被调用函数使用实参时的一个名称而已。不管是按值传递参数还是按地值传递参数,在调用过程用CALL指令调用被调用过程时,对应的实参应该都已有具体的值,并已将实参的值存放到调用过程的栈帧中作为入口参数,以等待被调用过程中的指令所用。例如,在程序一中,main函数调用swap函数的实参是&a和&b,在执行call指令调用swap之前,&a和&b的值分别是R[ebp]-4和R[ebp]-8。在程序二中,main函数调用swap函数的实参分别是a和b,在执行CALL指令调用swap之前,a和b的值分别是15和22。