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++中的名字重整技术的更多相关文章

  1. 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 ...

  2. Android逆向之旅---基于对so中的函数加密技术实现so加固

    一.前言 今天我们继续来介绍so加固方式,在前面一篇文章中我们介绍了对so中指定的段(section)进行加密来实现对so加固 http://blog.csdn.net/jiangwei0910410 ...

  3. Android逆向之旅---基于对so中的section加密技术实现so加固

    一.前言 好长时间没有更新文章了,主要还是工作上的事,连续加班一个月,没有时间研究了,只有周末有时间,来看一下,不过我还是延续之前的文章,继续我们的逆向之旅,今天我们要来看一下如何通过对so加密,在介 ...

  4. 【转】代码中特殊的注释技术——TODO、FIXME和XXX的用处

    (转自:http://blog.csdn.net/reille/article/details/7161942) 作者:reille 本博客网址:http://blog.csdn.net/reille ...

  5. 【Cocos2d-x游戏开发】Cocos2d-x中的数据存储技术

    一.引言 数据存储和网络功能可以说是一款游戏中必不可少的功能,如果一款游戏不能保存进度那么它的可玩性必然大打折扣(试想一下,玩家辛辛苦苦玩了一整天的游戏,结果退出时告诉人家不能保存关卡信息,你明天还得 ...

  6. OpenGL中实现双缓冲技术

    在OpenGL中实现双缓冲技术的一种简单方法: 1.在调用glutInitDisplayMode函数时, 开启GLUT_DOUBLE,即glutInitDisplayMode(GLUT_RGB | G ...

  7. Android中的接口回调技术

    Android中的接口回调技术有很多应用的场景,最常见的:Activity(人机交互的端口)的UI界面中定义了Button,点击该Button时,执行某个逻辑. 下面参见上述执行的模型,讲述James ...

  8. Linux 中的零拷贝技术,第 2 部分

    技术实现 本系列由两篇文章组成,介绍了当前用于 Linux 操作系统上的几种零拷贝技术,简单描述了各种零拷贝技术的实现,以及它们的特点和适用场景.第一部分主要介绍了一些零拷贝技术的相关背景知识,简要概 ...

  9. Linux 中的零拷贝技术,第 1 部分

    概述 本系列由两篇文章组成,介绍了当前用于 Linux 操作系统上的几种零拷贝技术,简单描述了各种零拷贝技术的实现,以及它们的特点和适用场景.本文是本系列文章的第一部分,主要是介绍一些零拷贝技术的相关 ...

随机推荐

  1. 排序:快速排序Quick Sort

    原理,通过一趟扫描将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序 ...

  2. 与table有关的布局

    当IE8发布时,它将支持很多新的CSS display属性值,包括与表格相关的属性值:table.table-row和table-cell,它也是最后一款支持这些属性值的主流浏览器.它标志着复杂CSS ...

  3. 看图说说Heap中对象的生存状态

  4. 无线破解那点事(PJ)

    有一段时间没有写博客了.只能说苦逼学生党伤不起啊,还好没挂-废话不说了,近期将会讲讲无线PJ那点事,也不是啥干货,就一些先前的笔记分享把. 0.无线网卡 想要提高破解效率,一块猛一点的USB无线网卡是 ...

  5. POJ 3685 Matrix (二分套二分)

    Matrix Time Limit: 6000MS   Memory Limit: 65536K Total Submissions: 8674   Accepted: 2634 Descriptio ...

  6. JAVA下查看进程中线程占用CPU较高的代码

    执行以下代码获得进程的线程.并找到CPU占用较高的线程ID ps -mp 16935 -o THREAD,tid,time   执行以下代码,通过线程ID获得16进制的线程标识 printf &quo ...

  7. ubuntu16.04 安装jdk 错误解决

    错误 $ apt-get install openjdk-9-jdk Errors were encountered while processing: /var/cache/apt/archives ...

  8. XML--将XML中数据提取出转换成表

    DECLARE @xml XMLSET @xml='<Students>    <Student  id="1001">        <name&g ...

  9. Spring jndi数据源配置方法

    xml配置: <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverMana ...

  10. Asp.net Core过滤器

    Asp.net Core五类过滤器:Authorization Filter.Resource Filter.Acton Filter.Exception Filter.Result Filter.优 ...