一、 函数参数传递机制的基本理论
函数参数传递机制问题在本质上是调用函数(过程)和被调用函数(过程)在调用发生时进行通信的方法问题。基本的
参数传递机制有两种:值传递和引用传递。以下讨论称调用其他函数的函数为主调函数,被调用的函数为被调函数。
值传递(passl-by-
value)过程中,被调函数的形式参数作为被调函数的局部变量处理,即在堆栈中开辟了内存空间以存放由主调函数放进来的实参的值,从而成为了实参的一个
副本。值传递的特点是被调函数对形式参数的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值。
引用传递(pass-by-
reference)过程中,被调函数的形式参数虽然也作为局部变量在堆栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函
数对形参的任何操作都被处理成间接寻址,即通过堆栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的
实
参变量。
二、 C语言中的函数参数传递机制
在C
语言中,值传递是唯一可用的参数传递机制。但是据笔者所知,由于受指针变量作为函数参数的影响,有许多朋友还认为这种情况是引用传递。这是错误的。请看下
面的代码:
int swap(int *x, int *y)
{
int temp;
temp = *x; *x =
*y; *y = temp;
return temp;
}
void main()
{
int a = 1, b =
2;
int *p1 = &a;
int *p2 = &b;
swap(p1, p2)
}
函数swap以两个指针变量作为参数,当main()调用swap时,是以值传递的方式将指针变量p1、p2的值(也就是变量a、b的地址)放在了
swap在堆栈中为形式参数x、y开辟的内存单元中。这一点从以下的汇编代码可以看出(注释是笔者加的):
22: void main()
23:
{
……
……
13: int a = 1, b = 2;
00401088 mov dword ptr
[ebp-4],1
0040108F mov dword ptr [ebp-8],2
14: int *p1 = &a;
00401096
lea eax,[ebp-4]
00401099 mov dword ptr [ebp-0Ch],eax
15: int *p2 =
&b;
0040109C lea ecx,[ebp-8]
0040109F mov dword ptr
[ebp-10h],ecx
16: swap(p1, p2);
004010A2 mov edx,dword ptr
[ebp-10h] ;参数p2的值进栈
004010A5 push edx
004010A6 mov eax,dword ptr
[ebp-0Ch] ;参数p1的值进栈
004010A9 push eax
004010AA call @ILT+15(swap)
(00401014) ;调用swap函数
004010AF add esp,8 ;清理堆栈中的参数
17: }
阅读上述代
码要注意,INTEL80x86系列的CPU对堆栈的处理是向下生成,即从高地址单元向低地址单元生成。从上面的汇编代码可知,main()在调用
swap之前,先将实参的值按从右至左的顺序压栈,即先p2进栈,再p1进栈。调用结束之后,主调函数main()负责清理堆栈中的参数。Swap
将使用这些进入堆栈的变量值。下面是swap函
数的汇编代码:
14: void swap(int *x, int *y)
15:
{
00401030 push ebp
00401031 mov ebp,esp ;ebp指向栈顶
……
……
16:
int temp;
17: temp = *x;
4: int temp;
5: temp = *x;
00401048
mov eax,dword ptr [ebp+8] ;操作已存放在堆栈中的p1,将p1置
; 入eax
0040104B mov
ecx,dword ptr [eax] ;通过寄存器间址将*p1置入ecx
0040104D mov dword ptr
[ebp-4],ecx;经由ecx将*p1置入temp变量的内
;存单元。以下类似
6: *x = *y;
00401050
mov edx,dword ptr [ebp+8]
00401053 mov eax,dword ptr [ebp+0Ch]
00401056
mov ecx,dword ptr [eax]
00401058 mov dword ptr [edx],ecx
7: *y =
temp;
0040105A mov edx,dword ptr [ebp+0Ch]
0040105D mov eax,dword
ptr [ebp-4]
00401060 mov dword ptr [edx],eax
8: return temp;
00401062
mov eax,dword ptr [ebp-4]
9: }
由上述汇编代码基本上说明了C语言中值传递的原理,只不过传递的是指
针的值而已。本文后面还要论述使用引用传递的swap函数。从这些汇编代码分析,这里我们可以得到以下几点:
1.
进程的堆栈存储区是主调函数和被调函数进行通信的主要区域。
2. C语言中参数是从右向左进栈的。
3.
被调函数使用的堆栈区域结构为:
局部变量(如temp)
返回地址
函数参数
低地址
高地址
4. 由主调函数在调用后清理堆栈。
5. 函数的返回值一般是放在寄存器中的。
这里尚需补充说明几点:
一是参数进栈的方式。对于内部类型,由于编译器知道各类型变量使用的内存大小故直接使用push指令;对于自定义的类型(如structure),采用从
源地址向目的(堆栈区)地址进行字节传送的方式入栈。二是函数返回值为什么一般放在寄存器中,这主要是为了支持中断;如果放在堆栈中有可能因为中断而被覆
盖。三是函数的返回值如果很大,则从堆栈向存放返回值的地址单元(由主调函数在调用前将此地址压栈提供给被调函数)进行字节传送,以达到返回的目的。对于
第二和第三点,《Thinking in
C++》一书在第10章有比较好的阐述。四是一个显而易见的结论,如果在被调函数中返回局部变量的地址是毫无意义的;因为局部变量存于堆栈中,调用结束后
堆栈将被清理,这些地址就变得无效了。
三、 C++语言中的函数参数传递机制
C++既有C的值传递又有引用传递。在值传递上与C一致,这里着重说明引用传递。如本文前面所述,引用传递就是传递变量的地址到被调函数使用的堆栈中。
在C++中声明引用传递要使用"&"符号,而调用时则不用。下面的代码是使用引用传递的swap2函数和main函数:
int&
swap2(int& x, int& y)
{
int temp;
temp = x;
x = y;
y
= temp;
return x;
}
void main()
{
int a = 1, b = 2;
swap2(a, b);
}
此时函数swap2将接受两个整型变量的地址,同时返回一个其中的一个。而从main函数中对swap2的调用swap2(a,
b)则看不出是否使用引用传递,是否使用引用传递,是由swap2函数的定义决定的。以下是main函数的汇编代码:
11: void
main()
12: {
……
……
13: int a = 1, b = 2;
00401088 mov
dword ptr [ebp-4],1 ;变量a
0040108F mov dword ptr [ebp-8],2 ;变量b
14:
swap2(a, b);
00401096 lea eax,[ebp-8] ;将b的偏移地址送入eax
00401099 push
eax ;b的偏移地址压栈
0040109A lea ecx,[ebp-4] ;将a的偏移地址送入ecx
0040109D
push ecx ;将a的偏移地址压栈
0040109E call @ILT+20(swap2) (00401019) ;调用swap函数
004010A3
add esp,8 ;清理堆栈中的参数
15: }
可以看出,main函数在调用swap2之前,按照从右至左的顺序将b和a的偏移地
址
压栈,这就是在传递变量的地址。此时swap2函数的汇编代码是:
2: int& swap2(int& x,
int& y)
3: {
00401030 push ebp
00401031 mov ebp,esp
……
……
4:
int temp;
5: temp = x;
00401048 mov eax,dword ptr [ebp+8]
0040104B
mov ecx,dword ptr [eax]
0040104D mov dword ptr [ebp-4],ecx
6: x =
y;
00401050 mov edx,dword ptr [ebp+8]
00401053 mov eax,dword ptr
[ebp+0Ch]
00401056 mov ecx,dword ptr [eax]
00401058 mov dword ptr
[edx],ecx
7: y = temp;
0040105A mov edx,dword ptr [ebp+0Ch]
0040105D
mov eax,dword ptr [ebp-4]
00401060 mov dword ptr [edx],eax
8:
return x;
00401062 mov eax,dword ptr [ebp+8] ;返回x,由于x是外部变量的偏移地
;址,
故返回是合法的
9: }
可以看出,swap2与前面的swap函数的汇编代码是一样的。这是因为前面的swap函数接受指针变量,而
指针变量的值正是地址。所以,对于这里的swap2和前面的swap来讲,堆栈中的函数参数存放的都是地址,在函数中操作的方式是一致的。但是,对
swap2来说这个地址是主调函数通过将实参变量的偏移地址压栈而传递进来的--这
是引用传递;而对swap来说,这个地址是主调函数通过将实参
变量的值压栈而传递进来的--这是值传递,只不过由于这个实参变量是指针变量所以其值是地址而已。
这里的关键点在于,同样是地址,一个是引用
传递中的变量地址,一个是值传递中的指针变量的值。我想若能明确这一点,就不至于将C语言中的以指针变量作为函数参数的值传递情况混淆为引用传递了。
虽然x是一个局部变量,但是由于其值是主调函数中的实参变量的地址,故在swap2中返回这个地址是合法的。
c++
中经常使用的是常量引用,如将swap2改为:
Swap2(const int& x; const int& y)
这时将不能在函数中修改引用地址所指向的内容,具体来说,x和y将不能出现在"="的左边。
四、 结束语
本文论述了在 C 和
c++ 中函数调用的参数传递机制;同时附带说明了函数返回值
的一些问题。本文示例使用的是VC++6.0。
分享到:
相关推荐
文章对可变参数函数的参数传递机制进行了剖析, 给出了准确、灵活设计可变参数函数的另一种方法
C/C++函数参数传递机制详解及实例 概要: C/C++的基本参数传递机制有两种:值传递和引用传递,我们分别来看一下这两种的区别。 (1)值传递过程中,需在堆栈中开辟内存空间以存放由主调函数放进来的实参的值,从而...
C/C++语言可变参数函数的参数传递机制剖析.pdf
C/C++语言可变参数函数的参数传递机制剖析[归纳].pdf
近来公司招人较多,由此面试了非常... C/C++函数参数的传递方式有三种:值传递(pass by value)、指针传递(pass bypointer)、引用传递(pass by reference)。 C/C++函数参数的传递通道是通过堆栈传递,默认遵
本文主要探讨C/C++语言函数间参数的传值和传地址机制,同时探讨了计算机在调用一个函数时采用什么样的方法来自适应的处理函数中的参数,即函数调用约定.
discuss how parameter passing is used in C/C++
C语言的函数入口参数,可以使用值传递和指针传递方式,C++又多了引用(reference)传递方式。引用传递方式在使用上类似于值传递,而其传递的性质又象是指针传递,这是C++初学者经常感到困惑的。为深入介绍这三种参数...
这些插件将 C/C++ 透视图添加到 Eclipse 工作台(Workbench)中, 现在后者可以用许多视图和向导以及高级编辑和调试支持来支持 C/C++ 开发。 由于其复杂性,CDT 被分成几个组件,它们都采用独立插件的形式。 每个...
用C/C++手工别写一个存根例程是个十分痛苦的差使,尤其当远程方法的参数中包含特定的数据结构(如:记录、数组 、图等)时。幸运的是,gSOAP包中的'wsdl2h'WSDL解析器和'soapcpp2’存根及架构编译器能够将web服务...
C++函数的原型中可以声明一个或多个带有默认值的参数。如果调用函数时,省略了相应的实际参数,那么编译器就会把默认值作为实际参数。可以这样来声明具有默认参数的C++函数原型: #include iostream.h void show...
8.4.3 在重载的构造函数中使用参数 8.4.4 基类和派生类的析构函数 8.4.5 医生也是人 8.4.6 关于派生类和基类构造函数的规则 8.5 多态和虚函数 8.5.1 多态——同一个接口,不同的行为 8.5.2 什么是虚函数 8.5.3 虚...
8.4.3 在重载的构造函数中使用参数 8.4.4 基类和派生类的析构函数 8.4.5 医生也是人 8.4.6 关于派生类和基类构造函数的规则 8.5 多态和虚函数 8.5.1 多态——同一个接口,不同的行为 8.5.2 什么是虚函数 8.5.3 虚...
这样声明之后,相当于告诉C, 函数const void f(void)是在C++语言的文件中声明或者实现的,c程序可以使用这个C++中的函数了,从而实现C++和c的混合编程。 13、编写一个函数,作用是把一个char组成的字符串...
7.3函数参数和返回值 7.3.1传递const值 7.3.2返回const值 7.3.3传递和返回地址 7.4类 7.4.1类里的const和enum 7.4.2编译期间类里的常量 7.4.3const对象和成员函数 7.4.4只读存储能力 7.5可变的(volatile) 7.6小结 ...
而且C++本身所具备的超越C语言的特性都可以使开发者编写出更易用,更灵活的代码。 在MFC中对消息的处理利用了消息映射的方法,该方法的基础是宏定义实现,通过宏定义将消息分派到不同的成员函数进行处理。下面简单...