lyyyuna 的小花园

动静中之动, by

RSS

c++ 函数重载是如何实现的

发表于 2016-12

函数重载是 c++ 的编译时多态的一部分,也就是说,该行为在编译完成后即是确定的。事实上,这是编译器和链接器之间玩的小花招。链接器通过符号(symbol)定位各个函数,所谓符号可以简单理解为一个字符串。

编译器会给每个函数名一个符号,在 c 语言中,符号名只和函数名有关。

来一个 c 语言程序的例子,使用 Visual Studio 编译

void add(int a, int b)
{}

int main()
{
	add(1, 2);
}

用 Visual Studio 自带的工具 dumpbin 查看 .obj 文件的符号表

017 00000000 SECT4  notype ()    External     | _add

我们换一个函数声明

void add(double a, double b)
{}

int main()
{
	add(1, 2);
}

再用 dumpbin 查看 .obj 文件的符号表

017 00000000 SECT4  notype ()    External     | _add

还是同样的符号。所以, c 语言编译器不支持函数重载,函数名相同的话,链接器永远只能看到一个名字。

那么,c++ 呢?

#include <iostream>
#include <string>

using namespace std;

void add(int a, int b)
{}
void add(double a, double b)
{}
void add(string a, string b)
{}

int main()
{
	add(1, 2);
	add(1.0, 2.0);
	add(string("1"), string("2"));

	return 0;
}

再用 dumpbin 查看 .obj 文件的符号表

2F3 00000000 SECT87 notype ()    External     | ?add@@YAXHH@Z (void __cdecl add(int,int))
2F4 00000000 SECT89 notype ()    External     | ?add@@YAXNN@Z (void __cdecl add(double,double))
2F5 00000000 SECT8B notype ()    External     | ?add@@YAXV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@0@Z (void __cdecl add(class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >))

可以看到,每个符号都不一样啦。这时候的函数声明不仅和函数名有关,也和参数类型有关,但和返回类型无关。符号能唯一确定,编译器自然也能顺利实现重载。

顺便可以发现,同一个函数声明在 c 和 c++ 中是完全不一样的。这也是为什么 c 和 c++ 之间动静态库不能直接互相调用的原因。为此 cpp 使用了 extern "C" 语法,强制使用 c++ 编译器使用 c 语言的符号命名方法。

我们实验一下

#include <iostream>
#include <string>

using namespace std;

extern "C"{
	void add(int a, int b)
	{}
}
void add(double a, double b)
{}
void add(string a, string b)
{}

int main()
{
	add(1, 2);
	add(1.0, 2.0);
	add(string("1"), string("2"));

	return 0;
}

查看 .obj 文件的符号表

2F3 00000000 SECTD3 notype ()    External     | _add
2F4 00000000 SECT87 notype ()    External     | ?add@@YAXNN@Z (void __cdecl add(double,double))
2F5 00000000 SECT89 notype ()    External     | ?add@@YAXV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@0@Z (void __cdecl add(class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >))

可以看到,第一个函数的符号和 c 语言一致了。