首页 / C++ / C++中的名字重整技术
C++中的名字重整技术
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了C++中的名字重整技术,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含8869字,纯文字阅读大概需要13分钟。
内容图文
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 3float func(float); 4 5int func(const std::vector<std::string>& vec); 6 7namespace NP1 8{ 9int func(int); 1011class C 12 { 13public: 14int func(int); 15 }; 16}; 1718namespace NP2 19{ 20int func(int); 2122class C 23 { 24public: 25int 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 30000000000400983 t _GLOBAL__I__Z4funci 4000000000040081a T _Z4funcRKSt6vectorISsSaISsEE 50000000000400802 T _Z4funcf 600000000004007f4 T _Z4funci 70000000000400838 T _ZN3NP11C4funcEi 80000000000400829 T _ZN3NP14funcEi 90000000000400858 T _ZN3NP21C4funcEi 10000000000040084a T _ZN3NP24funcEi 11 [lizheng@lzv6 c++]$ nm a.out | grep func | c++filt 120000000000400983 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&) 140000000000400802 T func(float) 15 00000000004007f4 T func(int) 160000000000400838 T NP1::C::func(int) 170000000000400829 T NP1::func(int) 180000000000400858 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命令,这里就有一个跨平台实现,
1 void UnDecorateName() 2 { 3 const size_t max_size = 1024; 4char szDecorateName[max_size] = {0}; 5char szUnDecorateName[max_size] = {0}; 6 printf("Please Input Mangled Name: "); 7 scanf("%s", szDecorateName); 8 9#ifdef WINDOWS_IMPL 10if (::UnDecorateSymbolName(szDecorateName, szUnDecorateName, sizeof(szUnDecorateName), UNDNAME_COMPLETE) == 0) 11 { 12 printf("UnDecorateSymbolName Failed. GetLastError() = %d", GetLastError()); 13 } 14else15 { 16 printf("Name after Mangled : %s \nName before Mangled : %s\n", szDecorateName, szUnDecorateName); 17 } 18 system("pause"); 19#else20int 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#endif25 }
总结:
名字重整在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 17int/*__cdecl*/ func(int);//windows平台下在函数名前可加__cdecl、__stdcall、__fastcall,默认__cdecl 18 19float func(float); 20 21int func(const std::vector<std::string>& vec); 22 23namespace NP1 24{ 25int func(int); 26 27class C 28 { 29public: 30int func(int); 31 }; 32}; 33 34namespace NP2 35{ 36int func(int); 37 38class C 39 { 40public: 41int func(int); 42 }; 43}; 44 45//#define IMPLEMENT_ALL /**打开该宏,则定义以上函数实现*/ 46#ifdef IMPLEMENT_ALL 47 48int func(int) { return1; } 49 50float func(float) { return (float)1.11; } 51 52int func(const std::vector<std::string>& vec) { return0; } 53 54namespace NP1 55{ 56int func(int) { return2; } 57 58int C::func(int) { return3; } 59}; 60 61namespace NP2 62{ 63int func(int) { return4; } 64 65int C::func(int) { return5; } 66}; 67 68#endif 69 70/****************************************************** 71根据重整后的名字解析出原函数原型名字(Windows/Linux) 72*******************************************************/ 73void UnDecorateName() 74{ 75const size_t max_size = 1024; 76char szDecorateName[max_size] = {0}; 77char szUnDecorateName[max_size] = {0}; 78 printf("Please Input Mangled Name: "); 79 scanf("%s", szDecorateName); 80 81#ifdef WINDOWS_IMPL 82if (::UnDecorateSymbolName(szDecorateName, szUnDecorateName, sizeof(szUnDecorateName), UNDNAME_COMPLETE) == 0) 83 { 84 printf("UnDecorateSymbolName Failed. GetLastError() = %d", GetLastError()); 85 } 86else 87 { 88 printf("Name after Mangled : %s \nName before Mangled : %s\n", szDecorateName, szUnDecorateName); 89 } 90 system("pause"); 91#else 92int 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 99100int main(void) 101{ 102int i = 1; 103float f = 1.0; 104 std::vector<std::string> vec; 105 NP1::C *pac = new NP1::C; 106 NP2::C *pbc = new NP2::C; 107108#if 0 109 func(f); 110 func(i); 111112 NP1::func(i); 113 pac->func(i); 114115 NP2::func(i); 116 pbc->func(i); 117118 func(vec); 119#endif120121 UnDecorateName(); 122return0; 123 }
原文:http://www.cnblogs.com/lizhenghn/p/3661643.html
内容总结
以上是互联网集市为您收集整理的C++中的名字重整技术全部内容,希望文章能够帮你解决C++中的名字重整技术所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。