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