在C/C++中, 一个程序要运行起来, 需要经历以下几个阶段: 预处理, 编译, 汇编, 链接.
而名字修饰是一种在编译过程中, 将函数, 变量的名称重新改变的机制, 简单来说就是编译器为了区分各个函数, 将函数通过某种算法, 重新修饰为一个全局唯一的名称.
在C语言当中的名字修饰规则非常简单, 只是在函数名字前面添加了下划线. 举个例子来说,看下面的代码
#include <stdio.h>
#include <stdlib.h>
int Add(int left, int right);
int main()
{
Add(1, 2);
system("pause");
return 0;
}
编译器报错:
上述的Add函数只给了声明没有给定义, 因此在最后链接的时候就会报错从报错的结果中可以看到, C语言只是简单的在函数名前添加下划线. 因此当工程中存在相同函数名的函数时,就会产生冲突.
但C++中不同于C语言, 由于C++要支持函数重载, 命名空间等, 从而导致其修饰规则也会变得比较复杂, 在不同的编译器中, 在底层的实现方式也可能都有差异. 举例来看
#include <iostream>
using namespace std;
int Add(int left, int right);
double Add(double left, double right);
int main()
{
Add(1, 2);
Add(1.0, 2.0);
system("pause");
return 0;
}
编译器报错:
通过上述报错可以看出, 编译器实际在底层使用的不是Add名字, 而是被重新修饰过的一个比较复杂的名字, 被重新修饰后的名字中包含了: 函数的名字以及参数类型. 这也就是为什么函数重载中几个同名函数要求其参数列表不同的原因, 只要参数列表不同, 编译器在编译时通过对函数名字进行重新修饰, 将参数类型包含在最终的名字中, 就可以保证名字在底层的全局唯一性.
常见的一些底层修饰
int func(int) 修饰后 ?func@@YAHH@Z
float func(float) 修饰后 ?func@@YAMM@Z
int C::func(int) 修饰后
?func@C@@AAEHH@Z
int C::C2::func(int) 修饰后
?func@C2@C@@AAEHH@Z
int N::func(int) 修饰后
?func@N@@YAHH@Z
int N::C::func(int) 修饰后
?func@C@N@@AAEHH@Z
总结来说, 在C++中, 名字修饰是由"?函数名@域名1@域名2…@@参数列表@Z"的格式构成的, 包含
a. 函数名
b. 所在域
c. 参数列表
也正因为如此, 在C++中, 以上三个必须完全相同, 才会出现冲突, 这也就是函数重载的原理.
关于 extern “C”
有时候在C++工程中我们可能会需要某些函数按照C的风格来编译, 在函数前加extern “C”, 意思就是告诉编译器, 将函数按照C语言规则来进行编译
比如, 创建一个C++工程, 代码如下
#include <iostream>
using namespace std;
int func(int a)
{
return a;
}
int func(int a, int b)
{
return a + b;
}
int main()
{
system("pause");
return 0;
}
这样直接执行不会产生错误, 接下来我们在此基础上进行修改
#include <iostream>
using namespace std;
extern "C"{
int func(int a)
{
return a;
}
int func(int a, int b)
{
return a + b;
}
}
int main()
{
system("pause");
return 0;
}
这样编译器就会报错