好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

c中函数参数传递

c中函数参数传递

c中函数参数传递

1 输入参数传递数组或地址

测试代码:

  1  #include <stdio.h>
  2  
  3   void  foo( char  * a)
   4   {
   5      fprintf(stdout,  "  %x %x %x\n  " , &a, a, a[ 0  ]);
   6   }
   7  
  8   int  main (  int  argc,  char  * argv[] )
   9   {
  10       char  a[ 20 ] = { '  a  ' ,  '  b  ' ,  '  c  '  };
  11  
 12      fprintf(stdout,  "  %x %x %x\n  " , &a, a, a[ 0  ]);
  13       foo(a);
  14       return   0  ;
  15  }                /*   ----------  end of function main  ----------   */ 

运行结果:

bfc5f31c bfc5f31c  61  
bfc5f2f0 bfc5f31c   61 

  运行结果的第二列和第三列都比较好理解,a仅仅是个地址,在main中并没有专门为a开辟一个空间来存放a这个地址,也可以说a就是一个地址的别名,它和指针不一样,指针是一个变量,只是这个变量里面存放的是地址而已,所以在main函数中对a取地址其实是没什么意义的,因为它本来就是个地址,所以编译器仅仅是把a输出。但当以a为参数调用foo函数时,一边我们都认为把一个地址传给了foo,foo里的a和main中的a是一样的。其实不然,foo里的a是一个指针变量,main中调用foo时,其实就是把main中a的值赋值给了foo中的a变量。所以本质上来讲,传地址和传值是没区别的,其实都是传值,区别在于这个值是不是个地址而已。不过c++的引用就不一样了,它是真正的对传递的参数取个别名,所以和传地址是不一样的。

  我们可以反汇编来看看上面的程序实际运行是怎么样的,先看main函数:

 08048422   <main>:
   8048422 :        55                        push     %ebp
   8048423 :        89  e5                    mov      %esp,%ebp
   8048425 :        83  e4 f0                 and      $0xfffffff0,%esp
   8048428 :        83  ec  40                  sub      $0x40,%esp
 8  04842b :       c7  44   24  2c  00   00   00      movl   $0x0,0x2c(%esp)
   8048432 :        00 
  8048433 :       c7  44   24   30   00   00   00      movl   $0x0,0x30(%esp)
 8  04843a :        00  
 8  04843b :       c7  44   24   34   00   00   00      movl   $0x0,0x34(%esp)
   8048442 :        00 
  8048443 :       c7  44   24   38   00   00   00      movl   $0x0,0x38(%esp)
 8  04844a :        00  
 8  04844b :       c7  44   24  3c  00   00   00      movl   $0x0,0x3c(%esp)
   8048452 :        00 
  8048453 :       c6  44   24  2c  61            movb   $0x61,0x2c(%esp)
   8048458 :       c6  44   24  2d  62            movb   $0x62,0x2d(%esp)
 8  04845d :       c6  44   24  2e  63            movb   $0x63,0x2e(%esp)
   8048462 :        0f  b6  44   24   2c          movzbl 0x2c(%esp),%eax
   8048467 :        0f   be c8                movsbl %al,%ecx
 8  04846a :       ba  84   85   04   08            mov      $0x8048584,%edx
 8  04846f :       a1 c0  97   04   08            mov      0x80497c0,%eax
   8048474 :        89  4c  24   10               mov      %ecx,0x10(%esp)
   8048478 :       8d 4c  24  2c              lea      0x2c(%esp),%ecx
 8  04847c :        89  4c  24   0c               mov      %ecx,0xc(%esp)
   8048480 :       8d 4c  24  2c              lea      0x2c(%esp),%ecx
   8048484 :        89  4c  24   08               mov      %ecx,0x8(%esp)
   8048488 :        89   54   24   04               mov      %edx,0x4(%esp)
 8  04848c :        89   04   24                  mov      %eax,(%esp)
 8  04848f :       e8 8c fe ff ff           call     8048320   <fprintf@plt>
   8048494 :       8d  44   24  2c              lea      0x2c(%esp),%eax
   8048498 :        89   04   24                  mov      %eax,(%esp)
 8  04849b :       e8  44  ff ff ff           call    8 0483e4   <foo>
 8  0484a0 :       b8  00   00   00   00            mov      $0x0,%eax
 8  0484a5 :       c9                       leave  
 8  0484a6 :       c3                       ret 

  函数中一些惯例语句我们直接跳过,我在 http://www.cnblogs.com/chengxuyuancc/archive/2013/05/28/3104769.html 中已经分析过了,我们直接看其它部分。首先编译器为数组char  a[20]在栈中分配空间,从804842b到804844b可以看出编译器为数组分配的空间是0x2c(%esp)~0x3c(%esp),这段代码主要是对数组清零。8048453~804848c主要是为调用函数fprintf,将参数压入栈中,从代码可以看出参数是从右向左依次压入栈的。8048494~8048498获取a的值并将a的值放入栈顶。

foo函数的反汇编代码:

 080483e4   <foo>:
 8  0483e4 :        55                        push     %ebp
 8  0483e5 :        89  e5                    mov      %esp,%ebp
 8  0483e7 :        53                        push     %ebx
 8  0483e8 :        83  ec  24                  sub      $0x24,%esp
 8  0483eb :       8b  45   08                  mov      0x8(%ebp),%eax
 8  0483ee :        0f  b6  00                  movzbl (%eax),%eax
 8  0483f1 :        0f   be d8                movsbl %al,%ebx
 8  0483f4 :       8b 4d  08                  mov      0x8(%ebp),%ecx
 8  0483f7 :       ba  84   85   04   08            mov      $0x8048584,%edx
 8  0483fc :       a1 c0  97   04   08            mov      0x80497c0,%eax
   8048401 :        89  5c  24   10               mov      %ebx,0x10(%esp)
   8048405 :        89  4c  24   0c               mov      %ecx,0xc(%esp)
   8048409 :       8d 4d  08                  lea      0x8(%ebp),%ecx
 8  04840c :        89  4c  24   08               mov      %ecx,0x8(%esp)
   8048410 :        89   54   24   04               mov      %edx,0x4(%esp)
   8048414 :        89   04   24                  mov      %eax,(%esp)
   8048417 :       e8  04  ff ff ff           call     8048320   <fprintf@plt>
 8  04841c :        83  c4  24                  add      $0x24,%esp
 8  04841f :       5b                       pop      %ebx
   8048420 :       5d                       pop      %ebp
   8048421 :       c3                       ret 

  在80483f4行中0x8(%ebp)指向的就是函数foo中的参数a的存储空间,正如前面所说的,foo中的a是一个指针变量,里面存放的是main中传过来的数组的地址。8048409则是获得a的地址值。

  从汇编代码中我们可以直观的看到main中的 a实际是一个地址的别名 ,它不占用存储空间,而它以参数传递给foo时,foo的接收参数 a是有存储空间的 。

  2 输入参数传递结构体

测试代码:

  1   #include <stdio.h>
   2  
  3   typedef struct test_p
   4   {
   5      char a[ 20 ] ;
   6  }test_p ;
   7  
  8   void foo(test_p a)
   9   {
  10      a.a[ 0 ] =  2  ;
  11      printf( "  %d\n  " , a.a[ 0 ]) ;
  12   }
  13  
 14   int  main (  int   argc, char *argv[] )
  15   {
  16      test_p a ;
  17  
 18      a.a[ 0 ] =  1  ;
  19      foo(a) ;
  20      printf( "  %d\n  " , a.a[ 0 ]) ;
  21      return  0  ;
  22  }               /* ----------  end of function main

运行结果:

?

2

1

   这个结果是很容易理解的,由于上面属于传值调用,也就是直接把main中的test_p结构体a拷贝一份赋值给foo中的test_p结构体a,从反汇编的代码中也可以看出实际也是这样的。

  3 输出参数传递结构体

   如果函数有返回值,一般传出参数放在寄存器eax中,从第一个例子中main函数的反汇编代码可以看出,main在返回之前将0赋值给了eax寄存器作为函数返回值。但如果返回值比较大,eax装不了怎么办?我们可以看看下面输出参数为结构体的情况。

测试代码:

  1  #include <stdio.h>
  2  
  3  typedef  struct   test_p
   4   {
   5       char  a[ 20  ];
   6   }test_p;
   7  
  8   test_p foo()
   9   {
  10       test_p a;
  11      a.a[ 0 ] =  1  ;
  12       return   a;
  13   }
  14  
 15   int  main (  int  argc,  char  * argv[] )
  16   {
  17       test_p a;
  18  
 19      a =  foo();
  20       return   0  ;
  21  }                /*   ----------  end of function main  ----------   */ 

反汇编代码:

 08048394   <foo>:
   8048394 :        55                        push     %ebp
   8048395 :        89  e5                    mov      %esp,%ebp
   8048397 :        83  ec  20                  sub      $0x20,%esp
 8  04839a :       c6  45  ec  01               movb   $0x1,-0x14(%ebp)
 8  04839e :       8b  45   08                  mov      0x8(%ebp),%eax
 8  0483a1 :       8b  55  ec                 mov      -0x14(%ebp),%edx
 8  0483a4 :        89   10                     mov      %edx,(%eax)
 8  0483a6 :       8b  55  f0                 mov      -0x10(%ebp),%edx
 8  0483a9 :        89   50   04                  mov      %edx,0x4(%eax)
 8  0483ac :       8b  55  f4                 mov      -0xc(%ebp),%edx
 8  0483af :        89   50   08                  mov      %edx,0x8(%eax)
 8  0483b2 :       8b  55  f8                 mov      -0x8(%ebp),%edx
 8  0483b5 :        89   50   0c                  mov      %edx,0xc(%eax)
 8  0483b8 :       8b  55  fc                 mov      -0x4(%ebp),%edx
 8  0483bb :        89   50   10                  mov      %edx,0x10(%eax)
 8  0483be :       8b  45   08                  mov      0x8(%ebp),%eax
 8  0483c1 :       c9                       leave  
 8  0483c2 :       c2  04   00                  ret      $0x4

  080483c5   <main>:
 8  0483c5 :        55                        push     %ebp
 8  0483c6 :        89  e5                    mov      %esp,%ebp
 8  0483c8 :        83  ec  24                  sub      $0x24,%esp
 8  0483cb :       8d  45  ec                 lea      -0x14(%ebp),%eax
 8  0483ce :        89   04   24                  mov      %eax,(%esp)
 8  0483d1 :       e8 be ff ff ff           call     8048394   <foo>
 8  0483d6 :        83  ec  04                  sub      $0x4,%esp
 8  0483d9 :       b8  00   00   00   00            mov      $0x0,%eax
 8  0483de :       c9                       leave  
 8  0483df :       c3                       ret 

  从反汇编的main函数中可以看出,在调用foo之前给foo传递了结构体变量a的地址。在foo函数在804839e~80483bb代码中将自己本地结构体变量的值赋给foo传递过来的变量,并将foo传递过来的变量的地址赋值给寄存器eax。也就是说main中的语句 a = foo()其实就相当于语句foo(&a) 。这样就很好的解决了返回参数过大的问题。

  上面讲的三种参数传递的情况算是c中较复杂的情况了,其它的情况都是同样的道理。从反汇编的代码中可以看出编译器为我们写的c代码做了很多优化的工作,就如上面的第三种情况,开始我认为foo函数在返回的时候应该另外开辟一段临时空间用以存放变量a的值,由于foo函数返回后变量a的空间就被释放了,在回到main函数后再将临时空间中存放的值赋值给a,不过编译器却巧妙的将a变量的地址传给foo函数,这样就节省了空间和时间。

 

 

分类:  c ,  汇编

作者: Leo_wl

    

出处: http://www.cnblogs.com/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于c中函数参数传递的详细内容...

  阅读:42次