C基礎----函數參數傳遞之值傳遞

    最近逛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一點關係都沒有,真的是一點點關係都沒有!這也就是爲何傳值不能改變傳遞的參數的值的原因!


    寫的有些亂,有疑問或者錯誤的地方可提出,謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章