函式呼叫約定與函式名稱修飾規則 VC

2021-05-28 16:55:22 字數 2362 閱讀 7539

[email protected]

使用c/c++語言開發軟體的程式設計師經常碰到這樣的問題:有時候是程式編譯沒有問題,但是鏈結的時候總是報告函式不存在(經典的lnk 2001錯誤),有時候是程式編譯和鏈結都沒有錯誤,但是只要呼叫庫中的函式就會出現堆疊異常。這些現象通常是出現在c和c++的**混合使用的情況下或在c++程式中使用第三方的庫的情況下(不是用c++語言開發的),其實這都是函式呼叫約定(calling convention)和函式名修飾(decorated name)規則惹的禍。函式呼叫方式決定了函式引數入棧的順序,是由呼叫者函式還是被呼叫函式負責清除棧中的引數等問題,而函式名修飾規則決定了編譯器使用何種名字修飾方式來區分不同的函式,如果函式之間的呼叫約定不匹配或者名字修飾不匹配就會產生以上的問題。本文分別對c和c++這兩種程式語言的函式呼叫約定和函式名修飾規則進行詳細的解釋,比較了它們的異同之處,並舉例說明了以上問題出現的原因。

函式呼叫約定不僅決定了發生函式呼叫時函式引數的入棧順序,還決定了是由呼叫者函式還是被呼叫函式負責清除棧中的引數,還原堆疊。函式呼叫約定有很多方式,除了常見的__cdecl,__fastcall和__stdcall之外,c++的編譯器還支援thiscall方式,不少c/c++編譯器還支援naked call方式。這麼多函式呼叫約定常常令許多程式設計師很迷惑,到底它們是怎麼回事,都是在什麼情況下使用呢?下面就分別介紹這幾種函式呼叫約定。

1.__cdecl

編譯器的命令列引數是/gd。__cdecl方式是c/c++編譯器預設的函式呼叫約定,所有非c++成員函式和那些沒有用__stdcall或__fastcall宣告的函式都預設是__cdecl方式,它使用c函式呼叫方式,函式引數按照從右向左的順序入棧,函式呼叫者負責清除棧中的引數,由於每次函式呼叫都要由編譯器產生清除(還原)堆疊的**,所以使用__cdecl方式編譯的程式比使用__stdcall方式編譯的程式要大很多,但是__cdecl呼叫方式是由函式呼叫者負責清除棧中的函式引數,所以這種方式支援可變引數,比如printf和windows的api wsprintf就是__cdecl呼叫方式。對於c函式,__cdecl方式的名字修飾約定是在函式名稱前新增乙個下劃線;對於c++函式,除非特別使用extern "c",c++函式使用不同的名字修飾方式。

2.__fastcall

編譯器的命令列引數是/gr。__fastcall函式呼叫約定在可能的情況下使用暫存器傳遞引數,通常是前兩個 dword型別的引數或較小的引數使用ecx和edx暫存器傳遞,其餘引數按照從右向左的順序入棧,被呼叫函式在返回之前負責清除棧中的引數。編譯器使用兩個@修飾函式名字,後跟十進位制數表示的函式引數列表大小,例如:@function_name@number。需要注意的是__fastcall函式呼叫約定在不同的編譯器上可能有不同的實現,比如16位的編譯器和32位的編譯器,另外,在使用內嵌彙編**時,還要注意不能和編譯器使用的暫存器有衝突。

3.__stdcall

編譯器的命令列引數是/gz,__stdcall是pascal程式的預設呼叫方式,大多數windows的api也是__stdcall呼叫約定。__stdcall函式呼叫約定將函式引數從右向左入棧,除非使用指標或引用型別的引數,所有引數採用傳值方式傳遞,由被呼叫函式負責清除棧中的引數。對於c函式,__stdcall的名稱修飾方式是在函式名字前新增下劃線,在函式名字後新增@和函式引數的大小,例如:

_functionname@number

4.thiscall

thiscall只用在c++成員函式的呼叫,函式引數按照從右向左的順序入棧,類例項的this指標通過ecx暫存器傳遞。需要注意的是thiscall不是c++的關鍵字,不能使用thiscall宣告函式,它只能由編譯器使用。

5.naked call

採用前面幾種函式呼叫約定的函式,編譯器會在必要的時候自動在函式開始新增儲存esi,edi,ebx,ebp暫存器的**,在退出函式時恢復這些暫存器的內容,使用naked call方式宣告的函式不會新增這樣的**,這也就是為什麼稱其為naked的原因吧。naked  call不是型別修飾符,故必須和_declspec共同使用。

vc的編譯環境預設是使用__cdecl呼叫約定,也可以在編譯環境的project setting...選單-》c/c++ =》code  generation項選擇設定函式呼叫約定。也可以直接在函式宣告前新增關鍵字__stdcall、__cdecl或__fastcall等單獨確定函式的呼叫方式。在windows系統上開發軟體常用到winapi巨集,它可以根據編譯設定翻譯成適當的函式呼叫約定,在win32中,它被定義為__stdcall。

3.檢視函式的名字修飾

有兩種方式可以檢查你的程式中的函式的名字修飾:使用編譯輸出列表或使用dumpbin工具。使用/fac,/fas或/facs命令列引數可以讓編譯器輸出函式或變數名字列表。使用dumpbin.exe /symbols命令也可以獲得obj檔案或lib檔案中的函式或變數名字列表。此外,還可以使用 undname.exe 將修飾名轉換為未修飾形式。

函式呼叫約定與函式名稱修飾規則 舉例

public virtual long stdcall cbasefilter queryvendorinfo wchar t queryvendorinfo cbasefilter uagjpapa w z public thiscall cbasevideorenderer cbasevideo...

函式呼叫約定與函式名稱修飾規則(一)

e mail 使用c c 語言開發軟體的程式設計師經常碰到這樣的問題 有時候是程式編譯沒有問題,但是鏈結的時候總是報告函式不存在 經典的lnk 2001錯誤 有時候是程式編譯和鏈結都沒有錯誤,但是只要呼叫庫中的函式就會出現堆疊異常。這些現象通常是出現在c和c 的 混合使用的情況下或在c 程式中使用第...

函式呼叫約定與函式名稱修飾規則(一)

e mail inte2000 163.com 使用c c 語言開發軟體的程式設計師經常碰到這樣的問題 有時候是程式編譯沒有問題,但是鏈結的時候總是報告函式不存在 經典的lnk 2001錯誤 有時候是程式編譯和鏈結都沒有錯誤,但是只要呼叫庫中的函式就會出現堆疊異常。這些現象通常是出現在c和c 的 混...