1. 概念
1.1 命名粉碎
命名粉碎(name mangling),又称命名修饰。name mangling是一种在编译器和链接器之间用于通信的符号协议,其目的在于按照程序中的语言规范,使符号具备足够的语义信息以保证链接过程准确无误的进行。
编译器将目标源文件中的名字进行调整,由于C++中支持overload和override,所以C++的编译器必须有name mangling把函数名和变量名进行调整。
1.2 方法重载
方法重载是指在一个类中定义多个同名的方法,但要求每个方法具有不同的参数的类型或者参数的个数。调用重载方法时,编译器会通过检查调用方法的参数类型以及个数选择一个恰当的方法。C++中支持方法重载,示例代码如程序清单1-1所示。
程序清单 1-1 方法重载示例
#include <iostream>
#include <iomanip>
using namespace std;
void Swap (int *a, int *b) /* 交换 int 变量的值 */
{
int temp = *a;
*a = *b;
*b = temp;
}
void Swap (float *a, float *b) /* 交换 float 变量的值 */
{
float temp = *a;
*a = *b;
*b = temp;
}
void Swap (char *a, char *b) /* 交换 char 变量的值 */
{
char temp = *a;
*a = *b;
*b = temp;
}
int main (int agrc, char **argv)
{
int n1 = 100, n2 = 200;
Swap(&n1, &n2); /* 交换 int 变量的值 */
cout<<n1<<", "<<n2<<endl;
float f1 = 12.5, f2 = 56.93;
Swap(&f1, &f2); /* 交换 float 变量的值 */
cout<<f1<<", "<<f2<<endl;
char c1 = 'A', c2 = 'B';
Swap(&c1, &c2); /* 交换 char 变量的值 */
cout<<c1<<", "<<c2<<endl;
return 0;
}
运行程序清单1-1中代码,执行结果如图1-1所示。
图 1-1 方法重载运行结果
注意:函数的返回值不能作为重载的依据,函数重载的规则:
1. 函数名字必须相同;
2. 参数列表不同(个数不同,类型不同,顺序不同);
3. 函数的返回值不能构成函数的重载。
2. 命名粉碎规则
2.1 gcc 和 g++ 编译器的命名粉碎
不同的编译器进行不同的方式进行name mangling ,下面介绍gcc和g++的name mangling 的规则,示例代码如程序清单2-1所示。
程序清单 2-1 示例代码
#include <stdio.h>
int fun_test_sylixos ( void )
{
return (0);
}
int fun_test_linux ( void )
{
return (0);
}
int main (int argc, char **argv)
{
fun_test_sylixos();
fun_test_linux();
return (0);
}
使用gcc编译器,生成目标文件,通过nm命令查看目标文件中的符号表。操作如图2-1所示。
图 2-1 gcc 编译
使用g++编译器,生成目标文件,通过nm命令查看目标文件中的符号表。操作如图2-2所示。
图 2-2 g++编译
由上述结果可知,gcc编译器编译后对应的符号表中几乎没有对函数进行修饰,而g++对函数的改编比较复杂。所以,如果一个由C语言编译的目标文件中调用了C++中实现的函数,肯定会出错的,因为符号不匹配。
这个时候可以使用C++的关键字“extern C”。对应的代码如程序清单2-2所示。
程序清单2-2 示例代码
#include <stdio.h>
#ifdef __cplusplus
Extern “C” {
#endif
int fun_test_sylixos ( void )
{
return (0);
}
int fun_test_linux ( void )
{
return (0);
}
#ifdef __cpluscplus
}
#endif
int main (int argc, char **argv)
{
fun_test_sylixos();
fun_test_linux();
return (0);
}
使用gcc编译器结果如图2-3所示。
图 2-3 gcc编译
使用g++编译器结果如图2-4所示。
图 2-4 g++ 编译
可见,使用关键字后,符号就按照C语言的格式来组织了。
2.2 命name mangling名规则
GNUC++的name mangling方案和其他C++编译器方案不同,所以一种编译器生成的目标文件并不能被另外一种编译器生成的目标文件使用。
简单对_Z16fun_test_sylixosv做个介绍:
1. C++语言中规定:以下划线并紧挨着大写字母开头或者以两个下划线开头的标识符都是C++语言中保留的标示符。所以_Z16fun_test_sylixosv是保留的标识符,g++编译的目标文件中的符号使用_Z开头(C99标准)。
2. 接下来的16表示接下来的要表示的一个字符串对象的长度(所以函数命名的时候不能用数字开头),所以fun_test_sylixos这16个字符就作为函数的名称被识别出来了。
3. 接下来的每个小写字母表示参数的类型,v表示void类型。
下面介绍C++中命名的修饰规则,以下为内置的编码类型:
<builtin-type> ::= v # void
::= w # wchar_t
::= b # bool
::= c # char
::= a # signed char
::= h # unsigned char
::= s # short
::= t # unsigned short
::= i # int
::= j # unsigned int
::= l # long
::= m # unsigned long
::= x # long long, __int64
::= y # unsigned long long, __int64
::= n # __int128
::= o # unsigned __int128
::= f # float
::= d # double
::= e # long double, __float80
::= g # __float128
::= z # ellipsis
::= u <source-name> # vendor extended type
一些其他的修饰规则请参照博客资料或者其他的参考资料。
3. 工具使用
3.1 nm
nm是编译器中的名字工具,列出目标文件中的符号表,下面介绍几种常用的用法:
nm – C:表示name mangling
nm –U:显示没有定义的符号
示例用法如图3-1所示。
图3-1 nm 查看符号表
nm的一些其他的用法请参照网上资料。
3.2 C++filt
c++filt 工具查看一个经过命名修饰后的符号,输出修饰之前的符号。示例用法如图3-2所示。
图3-4 c++filt工具
3.3__cxa_demangle()
#include<iostream>
#include<cxxabi.h>
using namespace std;
using namespace abi;
int main(int argc,char *argv[])
{
const char * mangled_string = "_Z16fun_test_sylixosv ";
char buffer[100];
int status;
size_t n = 100;
__cxa_demangle(mangled_string,buffer,&n,&status);
cout<<buffer<<endl;
cout<<status<<endl;
return 0;
}
执行结果如图3-5所示。
图 3-5 __cxa_demangle运行示例