C函数中的形式参数可以是基本类型变量名,构造类型变量名和指针类型变量名。对于不同的形式参数,其传递参数的方式不同,总体来说分成两种:按值传递和按地址传递。 当形参是基本类型变量名时,采用按值传递方式;当形参是指针类型变量名或者构造类型变量名时,采用按地址传递方式。 下面通过两个相似的程序说明二者的区别:
//程序一:按地址传递参数
#include <stdio.h>
swap(int *x,int *y){
int t=*x; *x=*y; *y=t;
}
main(){
int a=15,b=22;
printf(“a=%d\tb=%d\n”,a,b);
swap(&a,&b);
printf(“a=%d\tb=%d\n”,a,b);
}
/*//输出结果:
a=15 b=22
a=22 b=15
//程序二:按值传递参数
#include <stdio.h>
swap(int x,int y){
int t=x; x=y; y=t;
}
main(){
int a=15,b=22;
printf(“a=%d\tb=%d\n”,a,b);
swap(a,b);
printf(“a=%d\tb=%d\n”,a,b);
}
//输出结果:
a=15 b=22
a=15 b=22
可以看出,程序一实现了a和b的值的交换,而程序二并没有实现。 先看一下两个程序的栈帧内容有何差别: [:
//程序一汇编代码片段:
main:
leal -8(%ebp), %eax //将ebp-8里面的内容作为地址放到eax中
mov1 %eax, 4(%esp) //作为参数传到esp+4的位置
leal -4(%ebp), %eax //ebp-4传给eax,a=15
mov1 %eax, (%esp)
call swap
ret
swap:
//以下是准备阶段
push1 %ebp
mov1 %esp, %ebp
push1 %ebx //ebx是被调用者保存
//以下是过程体
mov1 8(%ebp), %edx //R[ecx]<-M[&a]=15
mov1 (%edx), %ecx //把15送到了ecx中
mov1 12(%ebp), %eax //R[ebx]<-M[&b]=22
mov1 (%eax), %ebx //把22送到ebx中
mov1 %ebx, (%edx) //ebx中的22替换掉a=15,之后a=22
mov1 %ecx, (%eax) //ecx中的15替换掉b=22,之后b=15
//以下是结束阶段
pop1 %ebx
pop1 %ebp
ret
//程序二汇编代码片段:
main:
leal -8(%ebp), %eax
mov1 %eax, 4(%esp)
leal -4(%ebp), %eax
mov1 %eax, (%esp)
call swap
ret
swap:
//以下是准备阶段
push1 %ebp
mov1 %esp, %ebp
//以下是过程体
mov1 8(%ebp), %edx
mov1 12(%ebp), %eax
mov1 (%eax), 8(%ebp)
mov1 %edx, 12(%ebp)
//以下是结束阶段
pop1 %ebp
ret
对比图: [ 程序一的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。