C++中的名字重整技術


C++ 一直為人詬病之一的原因是他的二進制模塊兼容性不好,即ABI(Application Binary Interface)問題。對於同一源代碼,不同編譯器,甚至同一編譯器不同版本都不兼容,其編譯出來的ABI不能相互使用。比如其中一個ABI問題是為了支持函數重載,C++使用了Name Mangling(翻譯為命名重整、名字改編、名字修飾等)技術,而Name Mangling在不同編譯器間基本是完全不兼容的。

Name Mangling是一種在編譯過程中,將函數、變量的名稱重新改編的機制,簡單來說就是編譯器為了區分各個函數,將函數通過一定算法,重新修飾為一個全局唯一的名稱。

C++除了支持函數重載,也即是允許多個函數擁有一樣的名字,同時也支持命名空間,也即同時允許多個同樣的函數定義在在不同的名稱空間。這使得Name mangling尤其復雜。

下面對此進行測試驗證。

說明:本文只簡單介紹在Windows平台Visual Studio 2010編譯器和Linux平台下GCC4.4.7編譯器下關於Name Mangling的異同。並不涉及過多原理內容,如想詳細了解Name Mangling的理論請參考以下兩篇文章:
             wikipedia Name_mangling
             Visual C++名字修飾

先看部分代碼(完整程序附在文章結尾處):

 1 int /*__cdecl*/ func(int);//windows平台下在函數名前可加__cdecl、__stdcall、__fastcall,默認__cdecl
 2 
 3 float  func(float); 
 4 
 5 int    func(const std::vector<std::string>& vec);
 6 
 7 namespace NP1
 8 {
 9     int func(int);
10 
11     class C 
12     {
13     public:
14         int func(int);
15     };
16 };
17 
18 namespace NP2
19 {
20     int func(int);
21 
22     class C
23     {
24     public:
25         int func(int);
26     };
27 };

對於上面這段代碼,只有同名的func函數,但是有重載的、全局的、不同名字空間的、不同類的。那么通過C++編譯器進行名字重整后的結果是什么呢,請繼續,

VS2010編譯以上代碼沒問題,但是在鏈接時顯示如下經典的error LNK2001: unresolved external symbol錯誤(你問我如何顯示的?不要實現函數,但在代碼中又調用該函數即可了,下面提供有所有源代碼):

1 error LNK2019: unresolved external symbol "int __cdecl func(class std::vector<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,class std::allocator<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > > > const &)" (?func@@YAHABV?$vector@V?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@V?$allocator@V?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@2@@std@@@Z) referenced in function _main
2 error LNK2019: unresolved external symbol "public: int __thiscall NP2::C::func(int)" (?func@C@NP2@@QAEHH@Z) referenced in function _main
3 error LNK2019: unresolved external symbol "int __cdecl NP2::func(int)" (?func@NP2@@YAHH@Z) referenced in function _main
4 error LNK2019: unresolved external symbol "public: int __thiscall NP1::C::func(int)" (?func@C@NP1@@QAEHH@Z) referenced in function _main
5 error LNK2019: unresolved external symbol "int __cdecl NP1::func(int)" (?func@NP1@@YAHH@Z) referenced in function _main
6 error LNK2019: unresolved external symbol "int __cdecl func(int)" (?func@@YAHH@Z) referenced in function _main
7 error LNK2019: unresolved external symbol "float __cdecl func(float)" (?func@@YAMM@Z) referenced in function _main

在上面的鏈接錯誤中,我們可以看到這同一組func函數分別被編譯器重新改名了,且每個名字是唯一的。

注意這里在函數名前增加了__cdecl標識,這也是Visual Studio平台下C++開發時的默認函數調用方式,另外還有兩種常用的是__stdcall、__fastcall,拿函數int func(int)來說,分別按__cdecl、__stdcall、__fastcall調用,其重整后的名字也不相同,分別如下:

1 ?func@@YAHH@Z  //__cdecl
2 ?func@@YGHH@Z  //__stdcall
3 ?func@@YIHH@Z  //__fastcall

簡單解釋一點,重整后的名字都以?開始,?后緊跟函數名,之后是兩個@@,第一個@表示函數名字結束,第二個@之后的第一個字母Y表示是全局函數,第二個字母A、G、I分別表示__cdecl、__stdcall、__fastcall,第三個字母H表示返回類型是整形,之后是多個參數類型表示(該函數只有一個整形參數,以H表示),參數結束以@表示,最后用Z表示名字結束。

詳細的重整規則可以參考文章:Visual C++名字修飾

對上面的代碼在GCC下編譯,鏈接報錯如下:

1 static_dynamic_polymorphic.cpp:(.text+0x4e): undefined reference to `func(float)'
2 static_dynamic_polymorphic.cpp:(.text+0x58): undefined reference to `func(int)'
3 static_dynamic_polymorphic.cpp:(.text+0x62): undefined reference to `NP1::func(int)'
4 static_dynamic_polymorphic.cpp:(.text+0x73): undefined reference to `NP1::C::func(int)'
5 static_dynamic_polymorphic.cpp:(.text+0x7d): undefined reference to `NP2::func(int)'
6 static_dynamic_polymorphic.cpp:(.text+0x8e): undefined reference to `NP2::C::func(int)'
7 static_dynamic_polymorphic.cpp:(.text+0x9a): undefined reference to `func(std::vector<std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::basic_string<char, std::char_traits<char>, std::allocator<char> > > > const&)'

從這里暫時看不出來名字重整后的結果,接下來會有的,不過話說GCC的報錯方式似乎總是比Visual Studio強,呵呵。

實現所有函數定義,然后在GCC下重新編譯並查看名字重整后的結果,

 1 [lizheng@lzv6 c++]$ g++ c++_name_mangling.cpp 
 2 [lizheng@lzv6 c++]$ nm a.out | grep func
 3 0000000000400983 t _GLOBAL__I__Z4funci
 4 000000000040081a T _Z4funcRKSt6vectorISsSaISsEE
 5 0000000000400802 T _Z4funcf
 6 00000000004007f4 T _Z4funci
 7 0000000000400838 T _ZN3NP11C4funcEi
 8 0000000000400829 T _ZN3NP14funcEi
 9 0000000000400858 T _ZN3NP21C4funcEi
10 000000000040084a T _ZN3NP24funcEi
11 [lizheng@lzv6 c++]$ nm a.out | grep func | c++filt
12 0000000000400983 t global constructors keyed to _Z4funci
13 000000000040081a T func(std::vector<std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::basic_string<char, std::char_traits<char>, std::allocator<char> > > > const&)
14 0000000000400802 T func(float)
15 00000000004007f4 T func(int)
16 0000000000400838 T NP1::C::func(int)
17 0000000000400829 T NP1::func(int)
18 0000000000400858 T NP2::C::func(int)
19 000000000040084a T NP2::func(int)

從上面可以清晰的看出函數名字重整后的命名,也可以直接根據重整后的名字反解析原函數原型說明,這個很有用處哦,不用不知道! 

對於GCC下名字重整規則,這里不詳述了,只做簡單介紹,比如_ZN3NP21C4funcEi,默認以_Z(G++規定)開頭,N表示有命名空間,數字表示后面跟着的幾個字符是一個整體,比如3NP2,表示后面有3個字符,即NP2,1C、4func也是這個意思。

同樣的,對於該函數原型(NP2::C::func(int))在Windows平台下重整后為?func@C@NP2@@QAEHH@Z,其名字空間、類名、函數名是通過@區分的,而不像GCC下的用數字標識。

從上面也可以看出,同樣的C++代碼,在不同編譯器下編譯后的名字重整並不一致,這必然C++的ABI不統一,也就導致C++的二進制代碼不能直接跨平台使用。

 對於如何從重整后的名字解析出函數原型聲明,也是有辦法的,比如上面linux下的c++filt命令(Windows平台下也有類似命令:undname.exe),這里就有一個跨平台的代碼實現,

 1 void UnDecorateName()
 2 {
 3     const size_t max_size = 1024;
 4     char szDecorateName[max_size] = {0};
 5     char szUnDecorateName[max_size] = {0};
 6     printf("Please Input Mangled Name: ");
 7     scanf("%s", szDecorateName);
 8 
 9 #ifdef WINDOWS_IMPL
10     if (::UnDecorateSymbolName(szDecorateName, szUnDecorateName, sizeof(szUnDecorateName), UNDNAME_COMPLETE) == 0)
11     {
12         printf("UnDecorateSymbolName Failed. GetLastError() = %d", GetLastError());
13     }
14     else
15     {
16         printf("Name after  Mangled : %s \nName before Mangled : %s\n", szDecorateName, szUnDecorateName);
17     }
18     system("pause");
19 #else
20     int status;
21     size_t n = max_size;
22     abi::__cxa_demangle(szDecorateName,szUnDecorateName,&n,&status); 
23     printf("Name after  Mangled : %s \nName before Mangled : %s\n", szDecorateName, szUnDecorateName);
24 #endif
25 }

總結:

名字重整在C++中必須的,不然也就沒法支持重載的概念了,但重整規則沒有統一標准,各編譯器廠商各自為政,相互之間很難兼容,尤其是在Windows、Linux平台間。因此我認為解決跨平台的最好辦法就是分別在相應平台相應編譯器下重新編譯。但有時候這又很難滿足,比如你使用了別人提供的C++二進制模塊,沒有對方源碼,也不清楚對方的編譯環境,呵呵,這就超過本文范圍了,再次在探討吧。

附上本次測試代碼,請參考,

  1 #include <iostream>
  2 #include <stdio.h>
  3 #include <vector>
  4 #include <string>
  5 
  6 #if defined(_WIN32) || defined(WIN32)        /**Windows*/
  7 #define WINDOWS_IMPL
  8 #include <Windows.h>
  9 #include <DbgHelp.h>                /*用於實現將重整后的名字解析為原始名字*/
 10 #pragma comment(lib,"DbgHelp.lib")
 11 #else
 12 #define LINUX_IMPL
 13 #include<cxxabi.h>                    /*用於實現將重整后的名字解析為原始名字*/
 14 #endif
 15 
 16 
 17 int /*__cdecl*/ func(int);//windows平台下在函數名前可加__cdecl、__stdcall、__fastcall,默認__cdecl
 18 
 19 float  func(float); 
 20 
 21 int    func(const std::vector<std::string>& vec);
 22 
 23 namespace NP1
 24 {
 25     int func(int);
 26 
 27     class C 
 28     {
 29     public:
 30         int func(int);
 31     };
 32 };
 33 
 34 namespace NP2
 35 {
 36     int func(int);
 37 
 38     class C
 39     {
 40     public:
 41         int func(int);
 42     };
 43 };
 44 
 45 //#define IMPLEMENT_ALL   /**打開該宏,則定義以上函數實現*/
 46 #ifdef IMPLEMENT_ALL
 47 
 48 int func(int) { return 1; }
 49 
 50 float func(float) { return (float)1.11; }
 51 
 52 int func(const std::vector<std::string>& vec) { return 0; }
 53 
 54 namespace NP1
 55 {
 56     int func(int) { return 2; }
 57     
 58     int C::func(int) { return 3; }
 59 };
 60 
 61 namespace NP2
 62 {
 63     int func(int) { return 4; }
 64 
 65     int C::func(int) { return 5; }
 66 };
 67 
 68 #endif
 69 
 70 /******************************************************
 71 根據重整后的名字解析出原函數原型名字(Windows/Linux)
 72 *******************************************************/
 73 void UnDecorateName()
 74 {
 75     const size_t max_size = 1024;
 76     char szDecorateName[max_size] = {0};
 77     char szUnDecorateName[max_size] = {0};
 78     printf("Please Input Mangled Name: ");
 79     scanf("%s", szDecorateName);
 80 
 81 #ifdef WINDOWS_IMPL
 82     if (::UnDecorateSymbolName(szDecorateName, szUnDecorateName, sizeof(szUnDecorateName), UNDNAME_COMPLETE) == 0)
 83     {
 84         printf("UnDecorateSymbolName Failed. GetLastError() = %d", GetLastError());
 85     }
 86     else
 87     {
 88         printf("Name after  Mangled : %s \nName before Mangled : %s\n", szDecorateName, szUnDecorateName);
 89     }
 90     system("pause");
 91 #else
 92     int status;
 93     size_t n = max_size;
 94     abi::__cxa_demangle(szDecorateName,szUnDecorateName,&n,&status); 
 95     printf("Name after  Mangled : %s \nName before Mangled : %s\n", szDecorateName, szUnDecorateName);
 96 #endif
 97 }
 98 
 99 
100 int main(void)
101 {
102     int i = 1;
103     float f = 1.0;
104     std::vector<std::string> vec;
105     NP1::C *pac = new NP1::C;
106     NP2::C *pbc = new NP2::C;
107 
108 #if 0
109     func(f);
110     func(i);
111 
112     NP1::func(i);
113     pac->func(i);
114 
115     NP2::func(i);
116     pbc->func(i);
117 
118     func(vec);
119 #endif
120 
121     UnDecorateName();
122     return 0;
123 }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM