淺談C語(yǔ)言函數(shù)調(diào)用參數(shù)壓棧的相關(guān)問題
參數(shù)入棧的順序
以前在面試中被人問到這樣的問題,函數(shù)調(diào)用的時(shí)候,參數(shù)入棧的順序是從左向右,還是從右向左。參數(shù)的入棧順序主要看調(diào)用方式,一般來(lái)說(shuō),__cdecl 和__stdcall 都是參數(shù)從右到左入棧。
看下面的代碼:
#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的系統(tǒng)下的運(yùn)行結(jié)果是:
address of a 1ec62c. address of b 1ec628.
32位Ubuntu的結(jié)果是:
address of a bfd03290. address of b bfd03294.
可以看出,首先,不同的體系結(jié)構(gòu),棧增長(zhǎng)的方向也不同,有的是從低地址向高地址方向增長(zhǎng),有的是從高地址向低地址方向增長(zhǎng)。
可以用以下的代碼來(lái)判斷棧的增長(zhǎng)方向:
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; } } }
函數(shù)調(diào)用時(shí)棧里都有什么
以參數(shù)從左到右入棧為例:
push arg0 -- High Address push arg1 ... push argn push eip push ebp -- Low address
32位系統(tǒng)和64位系統(tǒng)函數(shù)調(diào)用時(shí),參數(shù)入棧方式有不同么?
這個(gè)問題在不久之前被人問題,當(dāng)時(shí)傻了,我一直以來(lái)只關(guān)注過32位系統(tǒng)的參數(shù)入棧方式,一直以為64位系統(tǒng)也是一樣,沒有什么不同,現(xiàn)在歸納起來(lái)有兩點(diǎn):
64位系統(tǒng)先把傳入?yún)?shù)放在寄存器里面,在被調(diào)函數(shù)的具體實(shí)現(xiàn)中把寄存器的值入棧,然后再去棧中取參數(shù)
64位系統(tǒng)棧中參數(shù)存放的順序是從左至右的(因?yàn)橄冉?jīng)歷了寄存器傳值)
看下面的反匯編:
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操作系統(tǒng), 8048476: 的確是把參數(shù)直接入棧,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>
再來(lái)看64位的ubuntu操作系統(tǒng),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>
再來(lái)看64位系統(tǒng)test的實(shí)現(xiàn),先把edi入棧,再把esi入棧,這就是為什么函數(shù)看起來(lái)像是從左到右入棧的原因了。
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)
以上就是小編為大家?guī)?lái)的淺談C語(yǔ)言函數(shù)調(diào)用參數(shù)壓棧的相關(guān)問題的全部?jī)?nèi)容了,希望對(duì)大家有所幫助,多多支持腳本之家~
相關(guān)文章
C語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單五子棋游戲
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單五子棋游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11C語(yǔ)言進(jìn)度條的實(shí)現(xiàn)原理詳解
這篇文章主要介紹了C語(yǔ)言進(jìn)度條的實(shí)現(xiàn)原理,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08C語(yǔ)言中結(jié)構(gòu)體(struct)的幾種初始化方法
相信大家都知道struct結(jié)構(gòu)體是C語(yǔ)言中非常重要的復(fù)合類型,初始化的方法很多,那么小編下面對(duì)這些方法進(jìn)行總結(jié),便于自己和大家以后查閱,有需要的可以參考借鑒。2016-08-08關(guān)于C語(yǔ)言中弱符號(hào)與弱引用的實(shí)際應(yīng)用問題
在編碼過程中,我們經(jīng)常遇到符號(hào)重定義的錯(cuò)誤問題,本文通過實(shí)例代碼展示給大家介紹了C語(yǔ)言弱符號(hào)與弱引用的實(shí)際應(yīng)用問題,一起看看吧2021-09-09