小議三種函式呼叫約定
__cdecl
、__stdcall
、__fastcall
是c/c++
裡中經常見到的三種函式呼叫方式。其中__cdecl
是c/c++
預設的呼叫方式,__stdcall
是windows api
函式的呼叫方式,只不過我們在標頭檔案裡檢視這些api
的宣告的時候是用了winapi
的巨集進行代替了,而這個巨集其實就是__stdcall
了。三種呼叫方式的區別相信大家應該有些了解,這篇文章主要從例項和彙編的角度闡述這些區別的表現形態,使其對它們的區別認識從理論向實際過渡。
__cdecl: c/c++
預設方式,引數從右向左入棧,主調函式負責棧平衡。
__stdcall:windows api
預設方式,引數從右向左入棧,被調函式負責棧平衡。
__fastcall:
快速呼叫方式。所謂快速,這種方式選擇將引數優先從暫存器傳入(ecx
和edx
),剩下的引數再從右向左從棧傳入。因為棧是位於記憶體的區域,而暫存器位於cpu
內,故訪問方式快於記憶體,故其名曰「__fastcall
」。下面從例項來認識一下這三種呼叫約定。先來看乙個簡單的不能再簡單的程式了:
三個函式的內容都是一樣的,不同的是使用了三種呼叫的方式。我們先來看看在main
函式呼叫三個函式的時候的彙編**:
按照上面說的那樣,__cdecl
按照引數從右向左的方式進入棧區,注意fun1()
和fun3()
的區別,fun1()
在call fun1()
之後執行了add esp,8
。這一操作正是我們前面所說的進行棧的平衡。呼叫函式之前連續進行了兩次push
操作將函式所需的實參5
和2先後壓入了棧區,呼叫完成後,我們需要恢復呼叫前的狀態,則需調整棧頂指標esp
的位置,這一工作由誰來完成就決定了兩種函式呼叫方式__cdecl
(主調函式完成)和__stdcall
(被調函式完成)的產生。上圖我們看到了__cdecl
中由主調函式完成了,那麼__stdcall
呢,在被調函式fun3()
中,轉向被調函式結尾處的**,我們看到了這一句:
那麼fun1()
結尾處又是如何呢?
看到了吧,這個ret
指令後面跟沒跟值就決定了函式返回是棧指標esp
需要增加的量。這樣,不需要主調函式再呼叫add
指令為esp
操作平衡棧區,節約了程式的開銷,一條指令開銷小,如果十萬百萬個這樣的呼叫,這個開銷就明顯了。
說完了__cdecl
和__stdcall
,再來看看__fastcall
,如前面圖看到的呼叫時並未使用push
指令向棧裡傳引數,而是使用了
movedx, 5
movecx, 2
兩條指令。這樣直接將引數傳入暫存器,被調函式在執行的時候直接從暫存器取值即可,省去了從棧裡取出來給暫存器,再從暫存器取出來放入記憶體。
不過,說個題外話,ecx
暫存器經常作為計數和c++
裡this
指標的傳遞媒介。在這種情況下,情況又是怎樣的呢,下次分析c++
操作符new
的時候再予以討論。ecx
做計數器時,需要將ecx
中儲存的實參先壓入棧區,計數操作完成後再pop
出來。如此一來,這個fastcall
倒顯得不那麼fast
了。呵呵。
當然,上面所說的這些操作都是由編譯器在背後為我們完成的,開發人員無需關心這些操作,對我們是透明的。不過,知其然更知其所以然方能立於不敗之地!
三種函式呼叫約定
cdecl stdcall fastcall是c c 裡中經常見到的三種函式呼叫方式。其中 cdecl是c c 預設的呼叫方式,stdcall是windows api函式的呼叫方式,只不過我們在標頭檔案裡檢視這些api的宣告的時候是用了winapi的巨集進行代替了,而這個巨集其實就是 stdcall...
約定stdcall 函式呼叫的三種約定,你都清楚嗎
cdecl stdcall fastcall是c c 裡中經常見到的三種函式呼叫方式。其中 cdecl是c c 預設的呼叫方式,stdcall是windows api函式的呼叫方式,只不過我們在標頭檔案裡檢視這些api的宣告的時候是用了winapi的巨集進行代替了,而這個巨集其實就是 stdcall...
約定stdcall 函式呼叫的三種約定,你都清楚嗎
cdecl stdcall fastcall是c c 裡中經常見到的三種函式呼叫方式。其中 cdecl是c c 預設的呼叫方式,stdcall是windows api函式的呼叫方式,只不過我們在標頭檔案裡檢視這些api的宣告的時候是用了winapi的巨集進行代替了,而這個巨集其實就是 stdcall...