函式的工作借助於棧。
棧在記憶體中是一塊特殊的儲存空間,它的儲存原則是「先進後出」,最先被儲存的資料最後被釋放。
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時直...