最近逛CSDN多了,看到論壇中很多新手提的問題,不禁想寫個博客和大家分享一下。同時對自己也是一個複習。
我們還是直接通過代碼來講:
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一點關係都沒有,真的是一點點關係都沒有!這也就是爲何傳值不能改變傳遞的參數的值的原因!
寫的有些亂,有疑問或者錯誤的地方可提出,謝謝!