以前在面試中被人問到這樣的問題,函數調用的時候,參數入棧的順序是從左向右,還是從右向左。當時沒有想清楚,隨口就說從右向左。其實這個回答是不完全正確的。因為其實入棧的順序,不同的體系架構是不一樣的,舉例來說, 看下面的代碼:
#include
int test(int a, int b)
{
printf("address of a %x.\n", &a);
printf("address of b %x.\n", &b);
if ((unsigned int)&a > (unsigned int)&b) {
printf("Push argument from left to right...\n");
}
else {
printf("Push argument from right to left...\n");
}
return 0;
}
int main()
{
test(1, 2);
return 0;
}
在64位Ubuntu的系統下的運行結果是:從左到右。
address of a 1ec62c.
address of b 1ec628.
Push argument from left to right…
32位Ubuntu的結果是:從右到左
address of a bfd03290.
address of b bfd03294.
Push argument from right to left…
先來解釋一下為什麼上面的代碼能夠判別入棧的順序,首先你要明白一點:
C語言中的gcc編譯的棧是從高地址向低地址生長的,也就是說誰先入棧,誰的地址就大,掌握了這一點,應試不難寫出代碼.
以參數從左到右入棧為例:
push arg0 -- High Address
push arg1
...
push argn
push eip
push ebp -- Low address
這個問題在不久之前被人問題,當時傻了,我一直以來只關注過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
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
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
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
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
再來看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
再來看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)