函式的工作原理

2021-06-28 03:11:09 字數 4307 閱讀 6164

函式的工作借助於棧。

棧在記憶體中是一塊特殊的儲存空間,它的儲存原則是「先進後出」,最先被儲存的資料最後被釋放。

esp被稱為棧頂指標,ebp稱為棧底指標,通過這兩個指標暫存器儲存當前棧的起始位址與結束位址。

esp與ebp之間所構成的空間便成為棧幀。通常,在vc++中,棧幀中可以定址的資料有區域性變數、函式返回位址、函式引數等。不同的兩次函式呼叫,所形成的棧幀也不同。當由乙個函式進入到另乙個函式中時,就會針對所呼叫的函式形成所需的棧空間,形成此函式的棧幀。當這個函式結束呼叫時,需要清除掉它所使用的棧空間,關閉棧幀,這一過程稱為棧平衡。

int main()

彙編**講解

int main()

;在退出時,恢復原來的棧幀,ebp的值是原來的esp,然後再pop出ebp

00c813a0 pop edi

00c813a1 pop esi

00c813a2 pop ebx

00c813a3 mov esp,ebp ;還原esp

00c813a5 pop ebp ;還原ebp

00c813a6 ret

上面**在退出時並沒有檢測棧平衡,如下

00c813a0  pop         edi  

00c813a1 pop esi

00c813a2 pop ebx

多了這一段檢測

add esp,40h ;降低棧頂esp,此時區域性變數空間被釋放

cmp ebp,esp ;檢測棧平衡,如ebp與esp不等,則不平衡

call _chkesp ;進入棧平衡錯誤檢查函式

00c813a3 mov esp,ebp ;還原esp

00c813a5 pop ebp ;還原ebp

00c813a6 ret

在vc++環境下有三種函式呼叫約定:_cdecl、_stdcall、fastcall。

_cdecl:c/c++預設的呼叫方式,呼叫方平衡棧,不定引數的函式可以使用。

_stdcall:被呼叫方平衡棧,不定引數的函式無法使用。

_fastcall:暫存器方式傳參,被呼叫方平衡棧,不定引數的函式無法使用。

#includevoid _stdcall showstd(int number)

void _cdecl showcde(int number)

void main()

void _stdcall showstd(int number)

00391c99 pop edi

00391c9a pop esi

00391c9b pop ebx

00391c9c add esp,0c0h

00391ca2 cmp ebp,esp

00391ca4 call __rtc_checkesp (03911dbh)

}00391ca9 mov esp,ebp

00391cab pop ebp

00391cac ret 4 ;這裡結束後平衡棧頂4,相當於esp+4

void _cdecl showcde(int number)

003917b9 pop edi

003917ba pop esi

003917bb pop ebx

003917bc add esp,0c0h

003917c2 cmp ebp,esp

003917c4 call __rtc_checkesp (03911dbh)

}003917c9 mov esp,ebp

003917cb pop ebp

003917cc ret ;這裡直接返回並沒有自己平衡,當執行權到了呼叫方時平衡 a

void main()

0039254f xor eax,eax ;下面的棧幀關閉是main函式的

00392551 pop edi

00392552 pop esi

00392553 pop ebx

00392554 add esp,0c0h

0039255a cmp ebp,esp

0039255c call __rtc_checkesp (03911dbh)

00392561 mov esp,ebp

00392563 pop ebp

}00392564 ret

為什麼要平衡棧頂呢?以前我一直弄不明白,我一直認為當我呼叫函式執行時,該函式形成了自己的棧幀,儲存了它可以用到的區域性變數等等,當它結束時,直接恢復到原來的棧頂就可以了,也就是mov esp,ebp這句就可以了,但為什麼會在返回時還要對esp進行更改。現在弄明白了,原因是該函式有引數,在呼叫函式前,引數會先被壓入棧中,所以在函式結束後,該函式的棧幀也關閉了,但是呼叫方的棧幀中還儲存著剛才函式所需要的引數,現在它成了沒有用的資料,當然要把它踢出去。

當showcde函式呼叫結束後,黃色區域棧的資料也就沒用了,所以降低棧頂。

下面看一下用暫存器方式傳參方式,fastcall

#includevoid _fastcall showfast(int one, int two, int three, int four)

void main()

void _fastcall showfast(int one, int two, int three, int four)

00132fdd pop edi

00132fde pop esi

00132fdf pop ebx

00132fe0 add esp,0d8h

00132fe6 cmp ebp,esp

00132fe8 call __rtc_checkesp (01311dbh)

00132fed mov esp,ebp

00132fef pop ebp

00132ff0 ret 8 ;由於在傳參的時候使用個兩個暫存器幫助傳參,用棧傳參只用了兩個,故ret 8

void main()

00131c91 xor eax,eax

00131c93 pop edi

00131c94 pop esi

00131c95 pop ebx

00131c96 add esp,0c0h

00131c9c cmp ebp,esp

00131c9e call __rtc_checkesp (01311dbh)

00131ca3 mov esp,ebp

00131ca5 pop ebp

}00131ca6 ret

上面的注釋已經很明白了,另外需要注意,

在使用ebp相對定址定位引數3和4時,為什麼不是從ebp+4開始的,原因是在呼叫函式時,會將該call的下一條指令的位址壓入棧中,所以定位從ebp+8開始。

虛函式的工作原理

通常,編譯器處理虛函式的方法是 給每個物件新增乙個隱藏成員。隱藏成員中儲存 乙個指向函式位址陣列的指標。這種陣列稱為虛函式表 virtual function table,vtbl 虛函式表中儲存了為類物件進行宣告的虛函式的位址。總之,使用虛函式時,在記憶體和執行速度方面有一定的成本。包括 1 每個...

getchar函式工作原理

函式原型 int getchar void 使用者輸入一列字元後,回車 回車字元也在緩衝區中 getchar 是就開始從鍵盤緩衝區裡面讀資料 鍵盤緩衝區應該是個佇列儲存結構,先進先出 然後返回ascii碼,如失敗,一般返回 1,最後顯示在螢幕上,但是每次只能從鍵盤緩衝區讀乙個字元,然後返回乙個字元,...

Linux中的system()函式工作原理

一 linux中的system 函式源 include include include include int system const char cmdstring if pid fork 0 else if pid 0 else return status 當system接受的命令為null時直...