最近逛优快云多了,看到论坛中很多新手提的问题,不禁想写个博客和大家分享一下。同时对自己也是一个复习。
我们还是直接通过代码来讲:
1 #include <stdio.h>
2
3 int foo(int a, int b)
4 {
5 a = 1;
6 b = 2;
7
8 return a + b;
9 }
10
11
12 int main()
13 {
14 int a = 3;
15 int b = 4;
16
17 int c = foo(a, b);
18
19 printf("a = %d, b = %d\n", a, b);
20
21 return 0;
22 }
上面的代码很简单,函数foo的参数传递方式为传值。程序的输出为:
a = 3, b = 4
为何foo中更改了参数a和b的值,但是main函数中输出的a和b的值却没有改变?
是的,传值。那么传值为何不能更改他们的值呢?我们来看看生成的可执行文件反汇编的结果:
310 080483e4 <foo>:
311 80483e4: 55 push %ebp
312 80483e5: 89 e5 mov %esp,%ebp
313 80483e7: 83 ec 10 sub $0x10,%esp
314 80483ea: c7 45 f8 01 00 00 00 movl $0x1,-0x8(%ebp)
315 80483f1: c7 45 fc 02 00 00 00 movl $0x2,-0x4(%ebp)
316 80483f8: 8b 45 fc mov -0x4(%ebp),%eax
317 80483fb: 8b 55 f8 mov -0x8(%ebp),%edx
318 80483fe: 01 d0 add %edx,%eax
319 8048400: c9 leave
320 8048401: c3 ret
321
322 08048402 <main>:
323 8048402: 55 push %ebp
324 8048403: 89 e5 mov %esp,%ebp
325 8048405: 83 e4 f0 and $0xfffffff0,%esp // 16字节对齐
326 8048408: 83 ec 20 sub $0x20,%esp
327 804840b: c7 44 24 14 03 00 00 movl $0x3,0x14(%esp)
328 8048412: 00
329 8048413: c7 44 24 18 04 00 00 movl $0x4,0x18(%esp)
330 804841a: 00
331 804841b: 8b 44 24 18 mov 0x18(%esp),%eax // 为foo函数调用做准备
332 804841f: 89 44 24 04 mov %eax,0x4(%esp)
333 8048423: 8b 44 24 14 mov 0x14(%esp),%eax
334 8048427: 89 04 24 mov %eax,(%esp)
335 804842a: e8 b5 ff ff ff call 80483e4 <foo>
336 804842f: 89 44 24 1c mov %eax,0x1c(%esp)
337 8048433: b8 30 85 04 08 mov $0x8048530,%eax
338 8048438: 8b 54 24 18 mov 0x18(%esp),%edx
339 804843c: 89 54 24 08 mov %edx,0x8(%esp)
340 8048440: 8b 54 24 14 mov 0x14(%esp),%edx
341 8048444: 89 54 24 04 mov %edx,0x4(%esp)
342 8048448: 89 04 24 mov %eax,(%esp)
343 804844b: e8 b0 fe ff ff call 8048300 <printf@plt>
344 8048450: b8 00 00 00 00 mov $0x0,%eax
345 8048455: c9 leave
346 8048456: c3 ret
main函数中我们只看高亮部分(后面都是为printf函数准备的)。
这里来讲一个常识:sp和bp。
sp都知道,存储的栈顶的位置。bp则是当前函数的栈帧的底部的位置。
我们看看函数foo和main,最开始的部分都有:
push %ebp
mov %esp,%ebp
sub $0x10,%esp
这三行汇编的作用是建立一个新的栈帧(为这个函数!)。
如上图所示,call foo时,push &ebp在把ebp压栈的同时,esp也更改了。move %esp, %ebp则修改ebp为esp。其实这里就是新建了一个栈帧,栈帧的大小则是由sub $0x10, %esp决定的,即栈帧大小为16字节。
艹,掌控不了了,写博客真是需要个本事啊...
下面我们看看main的汇编,来看看变量的内存布局,以及参数是怎么传递给foo的。
上图中的汇编由上到下,每步执行后的结果在栈中可以看到结果。
上面的栈帧是main函数的,变量a、b存储在其中。在调用foo进行参数传递时,会将参数先入栈。我们发现此处入栈顺序是从右到左,b先入栈,a后入栈。在call foo时,会把"call foo"这条指令的下条指令(即"mov %eax,0x1c(%esp)")的地址压栈,以便返回时返回到这条指令。
进入foo函数后,首先做ebp的保存,以便函数执行完成后恢复main函数的栈帧位置。然后确定foo函数的栈帧(起始位置是当前的esp,结束位置是esp -= 0x10)。
最后就是执行“a = 1; b = 2”了。
从汇编指令我们发现,这里其实就是创建两个局部变量:a = 1, b = 2。和传递进来的那a和b一点关系都没有,真的是一点点关系都没有!这也就是为何传值不能改变传递的参数的值的原因!
写的有些乱,有疑问或者错误的地方可提出,谢谢!