C++憤恨者劄記3——函數呼叫慣例
函數呼叫慣例指的是,參數壓棧順序及彈棧位置的約定。這個約定在函式宣告時指定,如:
void __stdcall Fn(int arg1, int arg2);
其中__stdcall就是呼叫慣例,表示參數從右至左入棧,而函數自己負責參數彈棧工作。
還有一種常用約定為__cdecl,表示參數從右至左入棧,而函數調用者負責參數彈棧工作。
如果沒有指定呼叫慣例,編譯器會使用預設約定。VS中預設約定可以在工程屬性中設定:
VC++中,一般函數使用__cdecl約定,但類成員函數,包括建構函式,使用__stdcall約定(我不知道為什麼),從它們的反組譯碼代碼中可以看出。
源碼:
class Node{public:Node(){}void Fn( int a, int b ){}};void Fn(int a, int b){}void main(){Node n;n.Fn(1, 2);Fn( 1, 2);}
main與Fn反組譯碼代碼如下:
-------------------------------------------------------------------------------------------------------------------
函數呼叫慣例在彙編中,區別是巨大的,而在進階語言裡卻很少進入視野。下面給出一個因約定不一致導致程式崩潰的例子。程式要在debug版本下編譯,或release版下把/Od打上,禁止最佳化,否則可能看不到程式崩潰。
typedef void (__cdecl *CDeclType)(void*);void __stdcall Fn(void* p){}void MyCallBack( CDeclType pFn ){pFn(0);/*pFn的參數將會被兩次彈棧,破壞棧平衡MyCallBack將找不到返回地址*/}void main(){MyCallBack( (CDeclType)Fn );}