一、函数名称修饰规则
函数的名字修饰(Decorated Name)就是编译器在编译期间创建的一个字符串,用来指明函数的定义或原型。LINK程序或其他工具有时需要指定函数的名字修饰来定位函数的正确位置。由于c语言不支持函数重载,而c++语言支持函数重载,所以c和c++的函数名称修饰规则是不相同的。
以下面这个Add函数为例,来探讨一下c和c++语言的函数名称修饰规则。
int Add(int x, int y)
{
return x+y;
}
c语言的函数名称修饰规则:
[window系统下vs2010]:(在映射文件.map中可以查看,需要右击当前工程 --〉属性--〉配置属性--〉链接器 --〉调试 --〉生成映射文件 修改为 “是 (/MAP)” ,然后运行程序在当前工程的Debug文件夹中就会生成一个.map的文件)。
c语言将上面的函数名称处理为_Add,即其修饰规则为_函数名(函数名前加_)。
[Centos]:(利用objdump命令可以查看目标文件中的函数名字的修饰)。
c语言将上面的函数名称处理为Add,即为函数名。
c++语言的函数名称修饰规则:
[window系统下vs2010]:
c++语言将上面的函数名称处理为?Add@@YAHHH@Z
(1)其中‘?’标识函数名的开始,其后跟函数名;
(2)“@@YA”标识参数表的开始,其后跟的第一个字符代表函数的返回值类型,接下来的字符依次代表函数参数列表中各个参数的类型;
类型的代号:
x--void ,
d--char,
e--unsigned char,
f--short,
h--int,
i--unsigned int,
j--long,
k--unsigned long,
m--float,
n--double,
_n--bool,
... ...
所以@@yg后面的HHH分别代表三个int类型,其中第一个为函数返回值,上下两个为参数类型。
(3)@z标识整个名字结束。
[Centos]:
c++语言将上面的函数名称处理为_Z3Addii。
其中_Z代表开始,3代表函数名的字符个数,其后跟函数名(这里为Add),再加上参数类型(这里ii代表有两个整型的参数)。
通过上面的分析,我们发现不同的编译器对函数名称修饰的规则是不同的,但我们依然可以找到规律,c语言对函数名称修饰的处理只关注到了函数名;而c++语言除了函数名,还关注了函数的参数,通过对函数名称的修饰不同,编译器调用函数时所找的符号就不同,因而c++语言支持函数重载。也可得出构成函数重载的条件为函数名相同,参数不同,返回值类型可同可不同。
二、extern "C" 的作用
那么,就有一个问题,在c++文件中可以直接利用extern使用c文件中的代码吗?
Add.c
int Add(int x, int y)
{
return x+y;
}
Test.cpp
#include <stdio.h>
#include <stdlib.h>
extern int Add(int x, int y);
int main()
{
int z = Add(1, 2);
printf("%d\n", z);
system("pause");
return 0;
}
上面这样使用可以吗? 哎呦,坏了,出现了这个错误:error LNK2019: 无法解析的外部符号 "int __cdecl Add(int,int)" (?Add@@YAHHH@Z),该符号在函数 _main 中被引用。
我们来分析一下,Add函数在Add.c这个文件中,编译时Add函数被处理为_Add。在Test.cpp文件中我们告诉编译器我们在别的文件中定义了Add函数,但是Test.cpp这个文件将Add函数处理为?Add@@YAHHH@Z,所以当买你函数中调用到Add函数时编译器就在别的文件中一直找?Add@@YAHHH@Z,发现没有找到。其实在别的文件中Add函数被处理为了_Add,所以肯定找不到了,长的都不一样嘛!!
那么,为了让编译器能够找到.c文件中的函数,我们该怎么办呢??
拿出我们的法宝extern "C",就是将Test.c文件中extern int Add(int x, int y);变为extern "C" int Add(int x, int y);
使用extern "C" 相当于告诉编译器这部分代码使用c语言的规则进行编译和链接。这样cpp文件中的Add函数就会被处理为_Add,编译器就能在Add.c文件中找到它了。