前言
在編程中,理解函數調用約定和棧的機制對于編寫高效代碼、調試程序以及進行逆向工程至關重要。本文將深入探討 C 和 C++ 的調用約定,以及棧與平棧的相關知識。
C 調用約定
在 C 語言中,默認的調用約定是 cdecl
。cdecl
調用約定的特點如下:
- 參數傳遞:參數從右向左依次壓入棧中。
- 棧清理:調用者負責清理棧(即調用者在函數返回后負責平棧)。
- 返回值:返回值通常存放在
EAX
寄存器中。
示例:
int add(int a, int b) {return a + b;
}int main() {int result = add(3, 4); // 調用add函數return 0;
}
在匯編層面,調用 add(3, 4)
的代碼可能如下:
push 4 ; 第二個參數壓棧
push 3 ; 第一個參數壓棧
call add ; 調用add函數
add esp, 8 ; 調用者平棧,清理8字節的棧空間
C++ 調用約定
C++ 調用約定與 C 調用約定有所不同,主要體現在以下幾點:
名稱修飾(Name Mangling)
- C++ 編譯器會對函數名進行修飾(Name Mangling),以支持函數重載、命名空間等特性。例如,函數
int add(int a, int b)
可能會被修飾為_Z3addii
。 - C 語言沒有名稱修飾,函數名在編譯后保持不變。
thiscall
調用約定
在 C++ 中,非靜態成員函數的調用約定通常是 thiscall
。thiscall
調用約定的特點:
this
指針:this
指針通常通過ECX
寄存器傳遞。- 參數傳遞:其他參數從右向左壓入棧中。
- 棧清理:被調用函數負責清理棧。
示例:
class MyClass {
public:int add(int a, int b) {return a + b;}
};int main() {MyClass obj;int result = obj.add(3, 4); // 調用成員函數addreturn 0;
}
在匯編層面,調用 obj.add(3, 4)
的代碼可能如下:
lea ecx, [obj] ; 將this指針(即obj的地址)放入ECX寄存器
push 4 ; 第二個參數壓棧
push 3 ; 第一個參數壓棧
call ?add@MyClass@@QAEHHH@Z ; 調用成員函數add
棧與平棧
棧的基本概念
- 棧(Stack):棧是一種后進先出(LIFO)的數據結構,用于存儲函數調用時的局部變量、參數、返回地址等信息。
- 棧幀(Stack Frame):每個函數調用都會在棧上創建一個棧幀,用于存儲該函數的局部變量、參數等信息。
- 棧指針(ESP):
ESP
寄存器指向當前棧頂的位置。
平棧(Stack Cleanup)
平棧是指在函數調用結束后,清理棧上的參數,使棧恢復到函數調用前的狀態。不同的調用約定決定了由誰負責平棧:
-
cdecl
調用約定:- 調用者負責平棧:調用者在函數返回后使用
add esp, n
指令清理棧。 - 示例:
push 4 push 3 call add add esp, 8 ; 調用者平棧,清理8字節的棧空間
- 調用者負責平棧:調用者在函數返回后使用
-
stdcall
調用約定:- 被調用函數負責平棧:被調用函數在返回前使用
ret n
指令自動清理棧。 - 示例:
push 4 push 3 call add ; 被調用函數內部: ret 8 ; 被調用函數平棧,清理8字節的棧空間
- 被調用函數負責平棧:被調用函數在返回前使用
-
fastcall
調用約定:- 被調用函數負責平棧:被調用函數在返回前使用
ret n
指令自動清理棧。 - 示例:
mov ecx, 3 ; 第一個參數通過ECX寄存器傳遞 mov edx, 4 ; 第二個參數通過EDX寄存器傳遞 call add ; 被調用函數內部: ret 0 ; 沒有參數通過棧傳遞,無需清理棧
- 被調用函數負責平棧:被調用函數在返回前使用
-
thiscall
調用約定:- 被調用函數負責平棧:被調用函數在返回前使用
ret n
指令自動清理棧。 - 示例:
lea ecx, [obj] ; this指針通過ECX寄存器傳遞 push 4 ; 第二個參數壓棧 push 3 ; 第一個參數壓棧 call ?add@MyClass@@QAEHHH@Z ; 被調用函數內部: ret 8 ; 被調用函數平棧,清理8字節的棧空間
- 被調用函數負責平棧:被調用函數在返回前使用
總結
- C 調用約定:默認使用
cdecl
,調用者負責平棧。 - C++ 調用約定:默認使用
thiscall
,被調用函數負責平棧。 - 棧與平棧:棧用于存儲函數調用的局部變量和參數,平棧是清理棧的過程,不同的調用約定決定了由誰負責平棧。
理解這些調用約定和棧的機制對于編寫高效的代碼、調試程序以及進行逆向工程都非常重要。希望本文能幫助你更好地掌握這些知識,提升編程技能!
如果你覺得這篇文章對你有幫助,請點贊、收藏并分享給你的朋友們!