編譯器的函式名修飾規則

2021-06-20 03:09:23 字數 3056 閱讀 8799

函式的名字修飾(decorated name)就是編譯器在編譯期間建立的乙個字串,用來指明函式的定義或原型。link程式或其他工具有時需要指定函式的名字修飾來定位函式的正確位置。多數情況下程式設計師並不需要知道函式的名字修飾,link程式或其他工具會自動區分他們。當然,在某些情況下需要指定函式的名字修飾,例如在c++程式中,為了讓link程式或其他工具能夠匹配到正確的函式名字,就必須為過載函式和一些特殊的函式(如建構函式和析構函式)指定名字裝飾。另一種需要指定函式的名字修飾的情況是在匯程式設計序中呼叫c或c++的函式。如果函式名字,呼叫約定,返回值型別或函式引數有任何改變,原來的名字修飾就不再有效,必須指定新的名字修飾。c和c++程式的函式在內部使用不同的名字修飾方式。

1,c編譯器的函式名修飾規則 

__stdcall呼叫約定,編譯器和鏈結器會在輸出函式名前加上乙個下劃線字首,函式名後面加上乙個「@」符號和其引數的位元組數,例如_functionname@number。

__cdecl呼叫約定僅在輸出函式名前加上乙個下劃線字首,例如_functionname。

__fastcall呼叫約定在輸出函式名前加上乙個「@」符號,後面也是乙個「@」符號和其引數的位元組數,例如@functionname@number。 

2,c++編譯器的函式名修飾規則 

c++的函式名修飾規則有些複雜,但是資訊更充分,通過分析修飾名不僅能夠知道函式的呼叫方式,返回值型別,引數個數甚至引數型別。

不管__cdecl,__fastcall還是__stdcall呼叫方式,函式修飾都是以乙個「?」開始,後面緊跟函式的名字,再後面是參數列的開始標識和按照引數型別代號拼出的參數列。

對於__stdcall方式,參數列的開始標識是「@@yg」,對於__cdecl方式則是「@@ya」,對於__fastcall方式則是「@@yi」。

3,檢視函式的名字修飾 

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

4,函式呼叫約定和名字修飾規則不匹配引起的常見問題

(重點)

函式呼叫時如果出現堆疊異常,十有**是由於函式呼叫約定不匹配引起的。--特別是在dll庫的使用時

動態庫生成的時候採用的函式呼叫約定是__stdcall,所以編譯生成的a.dll中函式makefun的呼叫約定是_stdcall,也就是函式呼叫時引數從右向左入棧,函式返回時自己還原堆疊。現在某個程式模組b要引用a中的makefun,b和a一樣使用c++方式編譯,只是b模組的函式呼叫方式是__cdecl,由於b包含了a提供的標頭檔案中makefun函式宣告,所以makefun在b模組中被其它呼叫makefun的函式認為是__cdecl呼叫方式,b模組中的這些函式在呼叫完makefun當然要幫著恢復堆疊啦,可是makefun已經在結束時自己恢復了堆疊,b模組中的函式這樣多此一舉就引起了棧指標錯誤,從而引發堆疊異常。巨集觀上的現象就是函式呼叫沒有問題(因為引數傳遞順序是一樣的),makefun也完成了自己的功能,只是函式返回後引發錯誤。解決的方法也很簡單,只要保證兩個模組的在編譯時設定相同的函式呼叫約定就行了。 

在了解了函式呼叫約定和函式的名修飾規則之後,再來看在c++程式中使用c語言編譯的庫時經常出現的lnk 2001錯誤就很簡單了。還以上面例子的兩個模組為例,這一次兩個模組在編譯的時候都採用__stdcall呼叫約定,但是a.dll使用c語言的語法編譯的(c語言方式),所以a.dll的載入庫a.lib中makefun函式的名字修飾就是「_makefun@4」。b包含了a提供的標頭檔案中makefun函式宣告,但是由於b採用的是c++語言編譯,所以makefun在b模組中被按照c++的名字修飾規則命名為「?makefun@@ygjj@z」,編譯過程相安無事,鏈結程式時c++的鏈結器就到a.lib中去找「?makefun@@ygjj@z」,但是a.lib中只有「_makefun@4」,沒有「?makefun@@ygjj@z」,於是鏈結器就報告: error lnk2001: unresolved external symbol ?makefun@@ygjj@z 

解決的方法和簡單,就是要讓b模組知道這個函式是c語言編譯的,extern "c"可以做到這一點。乙個採用c語言編譯的庫應該考慮到使用這個庫的程式可能是c++程式(使用c++編譯器),所以在設計標頭檔案時應該注意這一點。通常應該這樣宣告標頭檔案: 

#ifdef _cplusplus 

extern "c"  

#endif 

這樣c++的編譯器就知道makefun的修飾名是「_makefun@4」,就不會有鏈結錯誤了。 

例子:

extern"c"

__declspec(dllexport)

int__cdecl fun2(inta,intb)

__declspec(dllexport)

int__fastcall fun3(inta,intb)

}

許多人不明白,為什麼我使用的編譯器都是vc的編譯器還會產生「error lnk2001」錯誤?其實,vc的編譯器會根據原始檔的副檔名選擇編譯方式,如果檔案的副檔名是「.c」,編譯器會採用c的語法編譯,如果副檔名是「.cpp」,編譯器會使用c++的語法編譯程式,所以,最好的方法就是使用extern "c"。 

C和C 編譯器的函式名修飾規則

c編譯器的函式名修飾規則 對於 stdcall呼叫約定,編譯器和鏈結器會在輸出函式名前加上乙個下劃線字首,函式名後面加上乙個 符號和其引數的位元組數 例如 functionname number。cdecl呼叫約定僅在輸出函式名前加上乙個下劃線字首 例如 functionname。fastcall呼...

編譯器的符號修飾規則

編譯器編譯源 生成目標檔案時,需要為每乙個變數 函式生成符號,儲存到符號表。在符號表中,每乙個符號必須唯一,因此要求源 中不能存在與其它檔案中的變數名 函式名相同的函式,包括使用到的庫中的函式。為了解決符號名衝突的問題,編譯器會對源 中的符號進行修飾,如unix下編譯生成的符號會在符號名前加下劃線 ...

C 函式名的修飾規則

我們知道在c 中有函式過載這樣乙個東西,當我們定義了幾個功能類似且函式名是一樣的函式的時候,只要它的引數列表不同,編譯是可以通過的,但是在c中是不可以的。double add double a,double b int add int a,char b char add char a,char b ...