函数参数与函数调用(彻底理解值传递与引用传递)

来源:互联网 发布:农村淘宝网店 编辑:程序博客网 时间:2024/05/04 11:34
函数调用的作用:
 ·用实参数向形式参数传递数据;
 ·为获得数据参数及在函数体中声明的变量分配存储空间;
 ·中断现行(调用)函数,把流程向被调用函数的入口处,开始执行被调函数。
当参数表为空时,表示该函数不从调用函数接受数据。


函数参数传递机制

  堆栈存储区是主调函数(过程)和被调用函数(过程)在调用发生时进行通信的主要区域。
  基本的参数传递机制有两种:值传递和引用传递。

值传递(passl-by-value): 被调函数的形式参数作为被调函数的局部变量处理,即在堆栈中开辟了

内存空间以存放由主调函数放进来的实参的值,从而成为了实参的一个副本。值传递的特点是被调

函数对形式参数的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值。

引用传递(pass-by-reference): 被调函数的形式参数虽然也作为局部变量在堆栈中开辟了内存空间

,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成

间接寻址,即通过堆栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做

的任何操作都影响了主调函数中的实参变量。

 

特殊情况:常参数的传递

     在应用中参数经常使用常指针和常引用,这样的参数称为常参数,使用参数不会更新摸个参数指向的对象,在参数传递的过程中就不需要执行复制构造函数,从而改善效率。

 c++ 中经常使用的是常量引用,如 Swap2(const int& x; const int& y)
 这时将不能在函数中修改引用地址所指向的内容,具体来说,x和y将不能出现在"="的左边。

 

实例:

修改指针参数的地址,(区分2级指针)

  int m=10;
  int*n=&m;
  void swap(int*&a)  
  {    
     a=n;   //a地址被n替换
  }    
/******
值传递 的参数有2种--指针变量和非指针变量。当形参为指针变量的时候,要区分于引用传递,指

针变量 通过值传递过来的是一个地址的值,在被调函数中,只是修改了内存地址中存放的值,而并

没有修改地址值。

在C语言中,值传递是唯一可用的参数传递机制。
******/

由于受指针变量作为函数参数的影响,被认为是引用传递。这是错误的。

 

[cpp] view plaincopyprint?
  1. 例代码:  
  2. int swap(int *x, int *y)  
  3. {  
  4. int temp;  
  5. temp = *x; *x = *y; *y = temp;  
  6. return temp;  
  7. }  
  8. void main()  
  9. {  
  10. int a = 1, b = 2;  
  11. int *p1 = &a;  
  12. int *p2 = &b;  
  13. swap(p1, p2)  
  14. }  
  15.   函数swap以两个指针变量作为参数,当main()调用swap时,是以值传递的方式将指针变量p1、p2的值(也就是变量a、b的地址)放在了swap在堆栈中为形式参数x、y开辟的内存单元中。这一点从以下的汇编代码可以看出(注释是笔者加的):  
  16. 22: void main()  
  17. 23: {  
  18. ……  
  19. ……  
  20. 13: int a = 1, b = 2;  
  21. 00401088 mov dword ptr [ebp-4],1  
  22. 0040108F mov dword ptr [ebp-8],2  
  23. 14: int *p1 = &a;  
  24. 00401096 lea eax,[ebp-4]  
  25. 00401099 mov dword ptr [ebp-0Ch],eax  
  26. 15: int *p2 = &b;  
  27. 0040109C lea ecx,[ebp-8]  
  28. 0040109F mov dword ptr [ebp-10h],ecx  
  29. 16: swap(p1, p2);  
  30. 004010A2 mov edx,dword ptr [ebp-10h] ;参数p2的值进栈  
  31. 004010A5 push edx  
  32. 004010A6 mov eax,dword ptr [ebp-0Ch] ;参数p1的值进栈  
  33. 004010A9 push eax  
  34. 004010AA call @ILT+15(swap) (00401014) ;调用swap函数  
  35. 004010AF add esp,8 ;清理堆栈中的参数  
  36. 17: }  
  37.   阅读上述代码要注意,INTEL80x86系列的CPU对堆栈的处理是向下生成,即从高地址单元向低地址单元生成。从上面的汇编代码可知,main()在调用swap之前,先将实参的值按从右至左的顺序压栈,即先p2进栈,再p1进栈。调用结束之后,主调函数main()负责清理堆栈中的参数。Swap 将使用这些进入堆栈的变量值。下面是swap函数的汇编代码:  
  38. 14: void swap(int *x, int *y)  
  39. 15: {  
  40. 00401030 push ebp  
  41. 00401031 mov ebp,esp ;ebp指向栈顶  
  42. ……  
  43. ……  
  44. 16: int temp;  
  45. 17: temp = *x;  
  46. 4: int temp;  
  47. 5: temp = *x;  
  48. 00401048 mov eax,dword ptr [ebp+8] ;操作已存放在堆栈中的p1,将p1置入eax  
  49. 0040104B mov ecx,dword ptr [eax] ;通过寄存器间址将*p1置入ecx  
  50. 0040104D mov dword ptr [ebp-4],ecx;经由ecx将*p1置入temp变量的内存单元。以下类似  
  51. 6: *x = *y;  
  52. 00401050 mov edx,dword ptr [ebp+8]  
  53. 00401053 mov eax,dword ptr [ebp+0Ch]  
  54. 00401056 mov ecx,dword ptr [eax]  
  55. 00401058 mov dword ptr [edx],ecx  
  56. 7: *y = temp;  
  57. 0040105A mov edx,dword ptr [ebp+0Ch]  
  58. 0040105D mov eax,dword ptr [ebp-4]  
  59. 00401060 mov dword ptr [edx],eax  
  60. 8: return temp;  
  61. 00401062 mov eax,dword ptr [ebp-4]  
  62. 9: }  

由上述汇编代码基本上说明了C语言中值传递的原理,只不过传递的是指针的值而已。本文后面还要论述使用引用传递的swap函数。从这些汇编代码分析,这里我们可以得到以下几点:
  1.进程的堆栈存储区是主调函数和被调函数进行通信的主要区域。
  2. C语言中参数是从右向左进栈的。
  3. 被调函数使用的堆栈区域结构为:
    局部变量(如temp)
    返回地址
    函数参数
    低地址
    高地址
  4. 由主调函数在调用后清理堆栈。
  5.函数的返回值一般是放在寄存器中的。
  补充说明几点:一是参数进栈的方式。对于内部类型,由于编译器知道各类型变量使用的内存大小故直接使用push指令;对于自定义的类型(如structure),采用从源地址向目的(堆栈区)地址进行字节传送的方式入栈。二是函数返回值为什么一般放在寄存器中,这主要是为了支持中断;如果放在堆栈中有可能因为中断而被覆盖。三是函数的返回值如果很大,则从堆栈向存放返回值的地址单元(由主调函数在调用前将此地址压栈提供给被调函数)进行字节传送,以达到返回的目的。对于第二和第三点,《Thinking inC++》一书在第10章有比较好的阐述。四是一个显而易见的结论,如果在被调函数中返回局部变量的地址是毫无意义的;因为局部变量存于堆栈中,调用结束后堆栈将被清理,这些地址就变得无效了。

 

C++语言中的函数参数传递机制


      C++既有C的值传递又有引用传递。在值传递上与C一致,这里着重说明引用传递。如本文前面所述,引用传递就是传递变量的地址到被调函数使用的堆栈中。在C++中声明引用传递要使用"&"符号,而调用时则不用。下面的代码是使用引用传递的swap2函数和main函数

 

[cpp] view plaincopyprint?
  1. int& swap2(int& x, int& y)  
  2. {  
  3. int temp;  
  4. temp = x;  
  5. x = y;  
  6. y = temp;  
  7. return x;  
  8. }  
  9. void main()  
  10. {  
  11. int a = 1, b = 2;  
  12. swap2(a, b);  
  13. }  
  14.   此时函数swap2将接受两个整型变量的地址,同时返回一个其中的一个。而从main函数中对swap2的调用swap2(a, b)则看不出是否使用引用传递,是否使用引用传递,是由swap2函数的定义决定的。以下是main函数的汇编代码:  
  15. 11: void main()  
  16. 12: {  
  17. ……  
  18. ……  
  19. 13: int a = 1, b = 2;  
  20. 00401088 mov dword ptr [ebp-4],1 ;变量a  
  21. 0040108F mov dword ptr [ebp-8],2 ;变量b  
  22. 14: swap2(a, b);  
  23. 00401096 lea eax,[ebp-8] ;将b的偏移地址送入eax  
  24. 00401099 push eax ;b的偏移地址压栈  
  25. 0040109A lea ecx,[ebp-4] ;将a的偏移地址送入ecx  
  26.   
  27. 0040109D push ecx ;将a的偏移地址压栈  
  28. 0040109E call @ILT+20(swap2) (00401019) ;调用swap函数  
  29. 004010A3 add esp,8 ;清理堆栈中的参数  
  30. 15: }  
  31. 可以看出,main函数在调用swap2之前,按照从右至左的顺序将b和a的偏移地  
  32. 址压栈,这就是在传递变量的地址。此时swap2函数的汇编代码是:  
  33. 2: int& swap2(int& x, int& y)  
  34. 3: {  
  35. 00401030 push ebp  
  36. 00401031 mov ebp,esp  
  37. ……  
  38. ……  
  39. 4: int temp;  
  40. 5: temp = x;  
  41. 00401048 mov eax,dword ptr [ebp+8]  
  42. 0040104B mov ecx,dword ptr [eax]  
  43. 0040104D mov dword ptr [ebp-4],ecx  
  44. 6: x = y;  
  45. 00401050 mov edx,dword ptr [ebp+8]  
  46. 00401053 mov eax,dword ptr [ebp+0Ch]  
  47. 00401056 mov ecx,dword ptr [eax]  
  48. 00401058 mov dword ptr [edx],ecx  
  49. 7: y = temp;  
  50. 0040105A mov edx,dword ptr [ebp+0Ch]  
  51. 0040105D mov eax,dword ptr [ebp-4]  
  52. 00401060 mov dword ptr [edx],eax  
  53. 8: return x;  
  54. 00401062 mov eax,dword ptr [ebp+8] ;返回x,由于x是外部变量的偏移地址,故返回是合法的  
  55. 9: }  

可以看出,swap2与前面的swap函数的汇编代码是一样的。这是因为前面的swap函数接受指针变量,而指针变量的值正是地址。所以,对于这里的swap2和前面的swap来讲,堆栈中的函数参数存放的都是地址,在函数中操作的方式是一致的。但是,对swap2来说这个地址是主调函数通过将实参变量的偏移地址压栈而传递进来的--这是引用传递;而对swap来说,这个地址是主调函数通过将实参变量的值压栈而传递进来的--这是值传递,只不过由于这个实参变量是指针变量所以其值是地址而已。
  这里的关键点在于,同样是地址,一个是引用传递中的变量地址,一个是值传递中的指针变量的值。我想若能明确这一点,就不至于将C语言中的以指针变量作为函数参数的值传递情况混淆为引用传递了。
  虽然x是一个局部变量,但是由于其值是主调函数中的实参变量的地址,故在swap2中返回这个地址是合法的。

 

四、 结束语
  本文论述了在 C 和 c++ 中函数调用的参数传递机制;同时附带说明了函数返回值的一些问题。本文示例使用的是VC++6.0。

 

可见值传递是传输了要传递的变量的一个副本,所以改变这个副本不会对调用函数造成影响,但是这个被调用函数一般有一个有用的返回值,也就是你用某个东西,在使用过程中,也许改变了它,但是时候后,你又保持原样给了人家。比如给你一个打好节的丝巾,你使用时换了另一种样式,照了像,还别人的时候,又按照人家的借你的样子还给人家,而这个照片就是需要得到的东西(类似返回值)。

而引用,就是将要传递的变量的地址传到了被调用函数中,如果在被调用函数中改变,那么就会在调用函数中改变。比如你借了人家布,如果你剪裁了不同的样式,那么还人家的样子就是你剪裁后的样子。一般c++可以使用值传递和引用传递,后者更多。因为这样不用另外在堆栈中开辟空间,而值传递就需要另外的开辟空间,对内存有一定的浪费。一般c中只使用值传递。

另外关于存储数据方面,一般是将局部变量,函数返回地址,函数参数放到堆栈中,而函数返回值一般放到寄存器中,为的是方便中断,如果有零时中断就可以直接从寄存器中处理,不用再进行压栈出栈操作。

//错误的 得到的是乱码
char *strchr(char *str,char character)

   char* substr;
   substr[0]=str[0];
   return substr;    
}

0 0
原创粉丝点击