1. 程式人生 > >C語言函式呼叫引數壓棧的相關問題

C語言函式呼叫引數壓棧的相關問題

引數入棧的順序

以前在面試中被人問到這樣的問題,函式呼叫的時候,引數入棧的順序是從左向右,還是從右向左。引數的入棧順序主要看呼叫方式,一般來說,__cdecl__stdcall 都是引數從右到左入棧。看下面的程式碼:

#include <stdio.h>

int test(int a, int b)
{
    printf("address of a %x.\n", &a);
    printf("address of b %x.\n", &b);
    return 0;
}

int main()
{
    test(1, 2);
    return
0; }

在64位Ubuntu的系統下的執行結果是:

address of a 1ec62c.
address of b 1ec628.

32位Ubuntu的結果是:

address of a bfd03290.
address of b bfd03294.

可以看出,首先,不同的體系結構,棧增長的方向也不同,有的是從低地址向高地址方向增長,有的是從高地址向低地址方向增長。可以用以下的程式碼來判斷棧的增長方向:

typedef enum {
    LOW_TO_HIGH,
    HIGH_TO_LOW,
    LEFT_TO_RIGHT,
    RIGHT_TO_LEFT,
}stack_direc_t;

int
stack_grow_direc() { static char *p = NULL; char c; if (p == NULL) { p = &c; stack_grow_direc(); } else { printf("First in stack address is %x.\n", p); printf("Second in stack address is %x.\n", &c); if (&c > p) { printf
("Stack grows from low address to high address!\n"); return LOW_TO_HIGH; } else { printf("Stack grows from high address to low address!\n"); return HIGH_TO_LOW; } } }

函式呼叫時棧裡都有什麼

以引數從左到右入棧為例:

push arg0 -- High Address
push arg1
...
push argn
push eip
push ebp  -- Low address

32位系統和64位系統函式呼叫時,引數入棧方式有不同麼?

這個問題在不久之前被人問題,當時傻了,我一直以來只關注過32位系統的引數入棧方式,一直以為64位系統也是一樣,沒有什麼不同,現在歸納起來有兩點:

  • 64位系統先把傳入引數放在暫存器裡面,在被調函式的具體實現中把暫存器的值入棧,然後再去棧中取引數
  • 64位系統棧中引數存放的順序是從左至右的(因為先經歷了暫存器傳值)

看下面的反彙編:

C程式碼同上面一樣
Ubuntu 32位反彙編:
int main()
{
 804846d:   55                      push   %ebp
 804846e:   89 e5                   mov    %esp,%ebp
 8048470:   83 e4 f0                and    $0xfffffff0,%esp
 8048473:   83 ec 10                sub    $0x10,%esp
    test(1, 2);
 8048476:   c7 44 24 04 02 00 00    movl   $0x2,0x4(%esp)
 804847d:   00 
 804847e:   c7 04 24 01 00 00 00    movl   $0x1,(%esp)
 8048485:   e8 8a ff ff ff          call   8048414 <test>
    return 0;
 804848a:   b8 00 00 00 00          mov    $0x0,%eax
}
int test(int a, int b)
{
 8048414:   55                      push   %ebp
 8048415:   89 e5                   mov    %esp,%ebp
 8048417:   83 ec 18                sub    $0x18,%esp
    printf("address of a %x.\n", &a);
 804841a:   b8 60 85 04 08          mov    $0x8048560,%eax
 804841f:   8d 55 08                lea    0x8(%ebp),%edx
 8048422:   89 54 24 04             mov    %edx,0x4(%esp)
 8048426:   89 04 24                mov    %eax,(%esp)
 8048429:   e8 12 ff ff ff          call   8048340 <printf@plt>

    return 0;
 8048466:   b8 00 00 00 00          mov    $0x0,%eax
}
Ubuntu 64位反彙編:
int main()
{
  40056e:   55                      push   %rbp
  40056f:   48 89 e5                mov    %rsp,%rbp
    test(1, 2);
  400572:   be 02 00 00 00          mov    $0x2,%esi
  400577:   bf 01 00 00 00          mov    $0x1,%edi
  40057c:   e8 ac ff ff ff          callq  40052d <test>
    return 0;
  400581:   b8 00 00 00 00          mov    $0x0,%eax
}
int test(int a, int b)
{
  40052d:   55                      push   %rbp
  40052e:   48 89 e5                mov    %rsp,%rbp
  400531:   48 83 ec 10             sub    $0x10,%rsp
  400535:   89 7d fc                mov    %edi,-0x4(%rbp)
  400538:   89 75 f8                mov    %esi,-0x8(%rbp)
    printf("address of a %x.\n", &a);
  40053b:   48 8d 45 fc             lea    -0x4(%rbp),%rax
  40053f:   48 89 c6                mov    %rax,%rsi
  400542:   bf 14 06 40 00          mov    $0x400614,%edi
  400547:   b8 00 00 00 00          mov    $0x0,%eax
  40054c:   e8 bf fe ff ff          callq  400410 <printf@plt>

    return 0;
  400567:   b8 00 00 00 00          mov    $0x0,%eax
}

看32位的ubuntu作業系統, 8048476: 的確是把引數直接入棧,2先入棧,1後入棧。

 8048476:   c7 44 24 04 02 00 00    movl   $0x2,0x4(%esp)
 804847d:   00 
 804847e:   c7 04 24 01 00 00 00    movl   $0x1,(%esp)
 8048485:   e8 8a ff ff ff          call   8048414 <test>

再來看64位的ubuntu作業系統,2 和1根本就沒有放入到棧中,而是放到了暫存器esi和edi中。

  40056f:   48 89 e5                mov    %rsp,%rbp
    test(1, 2);
  400572:   be 02 00 00 00          mov    $0x2,%esi
  400577:   bf 01 00 00 00          mov    $0x1,%edi
  40057c:   e8 ac ff ff ff          callq  40052d <test>

再來看64位系統test的實現,先把edi入棧,再把esi入棧,這就是為什麼函式看起來像是從左到右入棧的原因了。

  40052d:   55                      push   %rbp
  40052e:   48 89 e5                mov    %rsp,%rbp
  400531:   48 83 ec 10             sub    $0x10,%rsp
  400535:   89 7d fc                mov    %edi,-0x4(%rbp)
  400538:   89 75 f8                mov    %esi,-0x8(%rbp)