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++名字修饰
先看部分代码(完整程序附在文章结尾处):
int /*__cdecl*/ func(int);//windows平台下在函数名前可加__cdecl、__stdcall、__fastcall,默认__cdecl float func(float); int func(const std::vector<std::string>& vec); namespace NP1
{
int func(int); class C
{
public:
int func(int);
};
}; namespace NP2
{
int func(int); class C
{
public:
int func(int);
};
};
对于上面这段代码,只有同名的func函数,但是有重载的、全局的、不同名字空间的、不同类的。那么通过C++编译器进行名字重整后的结果是什么呢,请继续,
VS2010编译以上代码没问题,但是在链接时显示如下经典的error LNK2001: unresolved external symbol错误(你问我如何显示的?不要实现函数,但在代码中又调用该函数即可了,下面提供有所有源代码):
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@@@std@@V?$allocator@V?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@@@std@@@@@std@@@Z) referenced in function _main
error LNK2019: unresolved external symbol "public: int __thiscall NP2::C::func(int)" (?func@C@NP2@@QAEHH@Z) referenced in function _main
error LNK2019: unresolved external symbol "int __cdecl NP2::func(int)" (?func@NP2@@YAHH@Z) referenced in function _main
error LNK2019: unresolved external symbol "public: int __thiscall NP1::C::func(int)" (?func@C@NP1@@QAEHH@Z) referenced in function _main
error LNK2019: unresolved external symbol "int __cdecl NP1::func(int)" (?func@NP1@@YAHH@Z) referenced in function _main
error LNK2019: unresolved external symbol "int __cdecl func(int)" (?func@@YAHH@Z) referenced in function _main
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调用,其重整后的名字也不相同,分别如下:
?func@@YAHH@Z //__cdecl
?func@@YGHH@Z //__stdcall
?func@@YIHH@Z //__fastcall
简单解释一点,重整后的名字都以?开始,?后紧跟函数名,之后是两个@@,第一个@表示函数名字结束,第二个@之后的第一个字母Y表示是全局函数,第二个字母A、G、I分别表示__cdecl、__stdcall、__fastcall,第三个字母H表示返回类型是整形,之后是多个参数类型表示(该函数只有一个整形参数,以H表示),参数结束以@表示,最后用Z表示名字结束。
详细的重整规则可以参考文章:Visual C++名字修饰
对上面的代码在GCC下编译,链接报错如下:
static_dynamic_polymorphic.cpp:(.text+0x4e): undefined reference to `func(float)'
static_dynamic_polymorphic.cpp:(.text+0x58): undefined reference to `func(int)'
static_dynamic_polymorphic.cpp:(.text+0x62): undefined reference to `NP1::func(int)'
static_dynamic_polymorphic.cpp:(.text+0x73): undefined reference to `NP1::C::func(int)'
static_dynamic_polymorphic.cpp:(.text+0x7d): undefined reference to `NP2::func(int)'
static_dynamic_polymorphic.cpp:(.text+0x8e): undefined reference to `NP2::C::func(int)'
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下重新编译并查看名字重整后的结果,
[lizheng@lzv6 c++]$ g++ c++_name_mangling.cpp
[lizheng@lzv6 c++]$ nm a.out | grep func
t _GLOBAL__I__Z4funci
000000000040081a T _Z4funcRKSt6vectorISsSaISsEE
T _Z4funcf
00000000004007f4 T _Z4funci
T _ZN3NP11C4funcEi
T _ZN3NP14funcEi
T _ZN3NP21C4funcEi
000000000040084a T _ZN3NP24funcEi
[lizheng@lzv6 c++]$ nm a.out | grep func | c++filt
t global constructors keyed to _Z4funci
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&)
T func(float)
00000000004007f4 T func(int)
T NP1::C::func(int)
T NP1::func(int)
T NP2::C::func(int)
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),这里就有一个跨平台的代码实现,
void UnDecorateName()
{
const size_t max_size = ;
char szDecorateName[max_size] = {};
char szUnDecorateName[max_size] = {};
printf("Please Input Mangled Name: ");
scanf("%s", szDecorateName); #ifdef WINDOWS_IMPL
if (::UnDecorateSymbolName(szDecorateName, szUnDecorateName, sizeof(szUnDecorateName), UNDNAME_COMPLETE) == )
{
printf("UnDecorateSymbolName Failed. GetLastError() = %d", GetLastError());
}
else
{
printf("Name after Mangled : %s \nName before Mangled : %s\n", szDecorateName, szUnDecorateName);
}
system("pause");
#else
int status;
size_t n = max_size;
abi::__cxa_demangle(szDecorateName,szUnDecorateName,&n,&status);
printf("Name after Mangled : %s \nName before Mangled : %s\n", szDecorateName, szUnDecorateName);
#endif
}
总结:
名字重整在C++中必须的,不然也就没法支持重载的概念了,但重整规则没有统一标准,各编译器厂商各自为政,相互之间很难兼容,尤其是在Windows、Linux平台间。因此我认为解决跨平台的最好办法就是分别在相应平台相应编译器下重新编译。但有时候这又很难满足,比如你使用了别人提供的C++二进制模块,没有对方源码,也不清楚对方的编译环境,呵呵,这就超过本文范围了,再次在探讨吧。
附上本次测试代码,请参考,
#include <iostream>
#include <stdio.h>
#include <vector>
#include <string> #if defined(_WIN32) || defined(WIN32) /**Windows*/
#define WINDOWS_IMPL
#include <Windows.h>
#include <DbgHelp.h> /*用于实现将重整后的名字解析为原始名字*/
#pragma comment(lib,"DbgHelp.lib")
#else
#define LINUX_IMPL
#include<cxxabi.h> /*用于实现将重整后的名字解析为原始名字*/
#endif int /*__cdecl*/ func(int);//windows平台下在函数名前可加__cdecl、__stdcall、__fastcall,默认__cdecl float func(float); int func(const std::vector<std::string>& vec); namespace NP1
{
int func(int); class C
{
public:
int func(int);
};
}; namespace NP2
{
int func(int); class C
{
public:
int func(int);
};
}; //#define IMPLEMENT_ALL /**打开该宏,则定义以上函数实现*/
#ifdef IMPLEMENT_ALL int func(int) { return ; } float func(float) { return (float)1.11; } int func(const std::vector<std::string>& vec) { return ; } namespace NP1
{
int func(int) { return ; } int C::func(int) { return ; }
}; namespace NP2
{
int func(int) { return ; } int C::func(int) { return ; }
}; #endif /******************************************************
根据重整后的名字解析出原函数原型名字(Windows/Linux)
*******************************************************/
void UnDecorateName()
{
const size_t max_size = ;
char szDecorateName[max_size] = {};
char szUnDecorateName[max_size] = {};
printf("Please Input Mangled Name: ");
scanf("%s", szDecorateName); #ifdef WINDOWS_IMPL
if (::UnDecorateSymbolName(szDecorateName, szUnDecorateName, sizeof(szUnDecorateName), UNDNAME_COMPLETE) == )
{
printf("UnDecorateSymbolName Failed. GetLastError() = %d", GetLastError());
}
else
{
printf("Name after Mangled : %s \nName before Mangled : %s\n", szDecorateName, szUnDecorateName);
}
system("pause");
#else
int status;
size_t n = max_size;
abi::__cxa_demangle(szDecorateName,szUnDecorateName,&n,&status);
printf("Name after Mangled : %s \nName before Mangled : %s\n", szDecorateName, szUnDecorateName);
#endif
} int main(void)
{
int i = ;
float f = 1.0;
std::vector<std::string> vec;
NP1::C *pac = new NP1::C;
NP2::C *pbc = new NP2::C; #if 0
func(f);
func(i); NP1::func(i);
pac->func(i); NP2::func(i);
pbc->func(i); func(vec);
#endif UnDecorateName();
return ;
}
C++中的名字重整技术的更多相关文章
- Ajax跨域问题及解决方案 asp.net core 系列之允许跨越访问(Enable Cross-Origin Requests:CORS) c#中的Cache缓存技术 C#中的Cookie C#串口扫描枪的简单实现 c#Socket服务器与客户端的开发(2)
Ajax跨域问题及解决方案 目录 复现Ajax跨域问题 Ajax跨域介绍 Ajax跨域解决方案 一. 在服务端添加响应头Access-Control-Allow-Origin 二. 使用JSONP ...
- Android逆向之旅---基于对so中的函数加密技术实现so加固
一.前言 今天我们继续来介绍so加固方式,在前面一篇文章中我们介绍了对so中指定的段(section)进行加密来实现对so加固 http://blog.csdn.net/jiangwei0910410 ...
- Android逆向之旅---基于对so中的section加密技术实现so加固
一.前言 好长时间没有更新文章了,主要还是工作上的事,连续加班一个月,没有时间研究了,只有周末有时间,来看一下,不过我还是延续之前的文章,继续我们的逆向之旅,今天我们要来看一下如何通过对so加密,在介 ...
- 【转】代码中特殊的注释技术——TODO、FIXME和XXX的用处
(转自:http://blog.csdn.net/reille/article/details/7161942) 作者:reille 本博客网址:http://blog.csdn.net/reille ...
- 【Cocos2d-x游戏开发】Cocos2d-x中的数据存储技术
一.引言 数据存储和网络功能可以说是一款游戏中必不可少的功能,如果一款游戏不能保存进度那么它的可玩性必然大打折扣(试想一下,玩家辛辛苦苦玩了一整天的游戏,结果退出时告诉人家不能保存关卡信息,你明天还得 ...
- OpenGL中实现双缓冲技术
在OpenGL中实现双缓冲技术的一种简单方法: 1.在调用glutInitDisplayMode函数时, 开启GLUT_DOUBLE,即glutInitDisplayMode(GLUT_RGB | G ...
- Android中的接口回调技术
Android中的接口回调技术有很多应用的场景,最常见的:Activity(人机交互的端口)的UI界面中定义了Button,点击该Button时,执行某个逻辑. 下面参见上述执行的模型,讲述James ...
- Linux 中的零拷贝技术,第 2 部分
技术实现 本系列由两篇文章组成,介绍了当前用于 Linux 操作系统上的几种零拷贝技术,简单描述了各种零拷贝技术的实现,以及它们的特点和适用场景.第一部分主要介绍了一些零拷贝技术的相关背景知识,简要概 ...
- Linux 中的零拷贝技术,第 1 部分
概述 本系列由两篇文章组成,介绍了当前用于 Linux 操作系统上的几种零拷贝技术,简单描述了各种零拷贝技术的实现,以及它们的特点和适用场景.本文是本系列文章的第一部分,主要是介绍一些零拷贝技术的相关 ...
随机推荐
- 解决idea gradle构建Received fatal alert: handshake_failure问题
Gradle是一款强大的构建工具,但是搭建项目运行环境总是非常头痛,各种网络原因会导致项目不能成功的导入. 说一下这个问题的解决办法,折腾了很久终于解决了. javax.net.ssl.SSLHand ...
- SQL编程:group by合并结果字符串 ---> group_concat函数就能行
1.表结构 create table tt(id int,v varchar(30)); insert into tt values(1,'a'),(1,'b'),(2,'b ...
- SDJZUOJ迷宫问题
题目描述 小明置身于一个迷宫,请你帮小明找出从起点到终点的最短路程. 小明只能向上下左右四个方向移动. 输入格式 输入包含多组测试数据.输入的第一行是一个整数T,表示有T组测试数据. 每组输入的第一行 ...
- 笔记01 登录、常用配置参数、Action访问Servlet API 和设置Action中对象的值、命名空间和乱码处理、Action中包含多个方法如何调用
Struts2登录 1. 需要注意:Struts2需要运行在JRE1.5及以上版本 2. 在web.xml配置文件中,配置StrutsPrepareAndExecuteFilter或FilterDis ...
- 咏南中间件增加HTTPS.SYS支持
咏南中间件增加HTTPS.SYS支持 老客户可免费升级. HTTPS.SYS可以开发强大而稳定的REST SERVER. 微软在Windows Vista (server 2008) 以后使用http ...
- [leetcode] 12. Merge Sorted Array
这道题的无聊之处在于题目其实给了一些很奇怪的测试用例.比如他会给一些空的数组来,但是这个是不科学的,因为在C++中不允许定义一个空的列表,我们用的又不是那种糙又快的python,所以在这里我遇到了一些 ...
- c#格式化字符
1.格式化货币(跟系统的环境有关,中文系统默认格式化人民币,英文系统格式化美元) string.Format("{0:C}",0.2) 结果为:¥0.20 (英文操作系统结果:$0 ...
- Spring学习(五)——集成MyBatis
本篇我们将在上一篇http://www.cnblogs.com/wenjingu/p/3829209.html的Demo程序的基础上将 MyBatis 代码无缝地整合到 Spring 中. 数据库仍然 ...
- 在Windows Server 2008上部署免费的https证书
背景 后web时代,https加密的重要性不言而喻.主流浏览器均对http站点标记不安全,敦促web服务提供商尽快升级至https. 原先的https证书多由各大域名服务商提供,动辄成千上万的部署证书 ...
- 如何创建一个自己的.NET Core Global Tools
索引 NET Core应用框架之BitAdminCore框架应用篇系列 框架演示:https://www.bitadmincore.com 框架源码:https://github.com/chenyi ...