• 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
     804842b:       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)
     804843a:       00
     804843b:       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)
     804844a:       00
     804844b:       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)
     804845d:       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
     804846a:       ba 84 85 04 08          mov    $0x8048584,%edx
     804846f:       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
     804847c:       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)
     804848c:       89 04 24                mov    %eax,(%esp)
     804848f:       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)
     804849b:       e8 44 ff ff ff          call   80483e4 <foo>
     80484a0:       b8 00 00 00 00          mov    $0x0,%eax
     80484a5:       c9                      leave
     80484a6:       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>:
     80483e4:       55                      push   %ebp
     80483e5:       89 e5                   mov    %esp,%ebp
     80483e7:       53                      push   %ebx
     80483e8:       83 ec 24                sub    $0x24,%esp
     80483eb:       8b 45 08                mov    0x8(%ebp),%eax
     80483ee:       0f b6 00                movzbl (%eax),%eax
     80483f1:       0f be d8                movsbl %al,%ebx
     80483f4:       8b 4d 08                mov    0x8(%ebp),%ecx
     80483f7:       ba 84 85 04 08          mov    $0x8048584,%edx
     80483fc:       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
     804840c:       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>
     804841c:       83 c4 24                add    $0x24,%esp
     804841f:       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
     804839a:       c6 45 ec 01             movb   $0x1,-0x14(%ebp)
     804839e:       8b 45 08                mov    0x8(%ebp),%eax
     80483a1:       8b 55 ec                mov    -0x14(%ebp),%edx
     80483a4:       89 10                   mov    %edx,(%eax)
     80483a6:       8b 55 f0                mov    -0x10(%ebp),%edx
     80483a9:       89 50 04                mov    %edx,0x4(%eax)
     80483ac:       8b 55 f4                mov    -0xc(%ebp),%edx
     80483af:       89 50 08                mov    %edx,0x8(%eax)
     80483b2:       8b 55 f8                mov    -0x8(%ebp),%edx
     80483b5:       89 50 0c                mov    %edx,0xc(%eax)
     80483b8:       8b 55 fc                mov    -0x4(%ebp),%edx
     80483bb:       89 50 10                mov    %edx,0x10(%eax)
     80483be:       8b 45 08                mov    0x8(%ebp),%eax
     80483c1:       c9                      leave
     80483c2:       c2 04 00                ret    $0x4
    
    080483c5 <main>:
     80483c5:       55                      push   %ebp
     80483c6:       89 e5                   mov    %esp,%ebp
     80483c8:       83 ec 24                sub    $0x24,%esp
     80483cb:       8d 45 ec                lea    -0x14(%ebp),%eax
     80483ce:       89 04 24                mov    %eax,(%esp)
     80483d1:       e8 be ff ff ff          call   8048394 <foo>
     80483d6:       83 ec 04                sub    $0x4,%esp
     80483d9:       b8 00 00 00 00          mov    $0x0,%eax
     80483de:       c9                      leave
     80483df:       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函数,这样就节省了空间和时间。

  • 相关阅读:
    K短路 (A*算法) [Usaco2008 Mar]牛跑步&[Sdoi2010]魔法猪学院
    [Noi2015]软件包管理器 BZOJ4196
    [SDOI2011]染色 BZOJ2243 树链剖分+线段树
    序列操作 BZOJ2962 线段树
    斜率优化入门学习+总结 Apio2011特别行动队&Apio2014序列分割&HZOI2008玩具装箱&ZJOI2007仓库建设&小P的牧场&防御准备&Sdoi2016征途
    BZOJ1854: [Scoi2010]游戏 二分图
    BZOJ3613: [Heoi2014]南园满地堆轻絮
    BZOJ4590: [Shoi2015]自动刷题机
    [JSOI2008]星球大战starwar BZOJ1015
    Rmq Problem/mex BZOJ3339 BZOJ3585
  • 原文地址:https://www.cnblogs.com/chengxuyuancc/p/3107054.html
Copyright © 2020-2023  润新知