在C++中,函数重载通常是如何实现的?

18

如果没有函数重载,函数名就充当了函数代码的地址。在调用函数时,可以使用其名称轻松找到地址。然而,如果有函数重载,程序怎么准确地找到正确的函数地址呢?是否有一个类似于虚表的隐藏表存储了重载函数及其地址?非常感谢!


6
建议对名称进行混淆的人我认为是误导了。编译器不是只混淆名称然后在混淆的名称之间查找。它需要从可用的方法中推断出正确的类型。一旦它做到这一点,就已经知道要调用哪个方法了。然后,它使用混淆的名称作为最后一步。名称混淆不是确定调用哪个重载函数的先决条件。 - Aryabhatta
也许这个评论应该放在你所提到的错误答案之一? - Manuel
有太多了,所以我就把它放在这里了。 - Aryabhatta
2
完全正确。给定 void foo(int);void foo(std::string);foo(1.0f) 将调用第一个函数。如果按照“名称修饰”的建议,编译器将寻找 ? foo(float) 并失败。 - MSalters
如果你想要一个重载函数的地址,有时候你必须将该地址转换为适当的类型。假设你有两个函数foo,一个接受int参数,一个接受float参数,并且你想要那个接受int参数的函数的地址,你可以写成static_cast<void(*)(int)>(&foo) - fredoverflow
感谢大家非常及时和有帮助的回答!!! - Rick
8个回答

13

名称修饰是指在编译时完成的。C++编译器实际上会在内部修改您提供的函数名称,以便像下面这样的函数:

int foo(int a, float b, char c) 

内部获取一个等价于名称的名称

func_foo_int_float_char()

(实际符号通常是一些像?CFoo@Foo@@QAAX_N@Z这样的无意义字符串).

可以看到,函数名称会根据传递的参数数量和类型进行修饰。因此,当您调用一个函数时,编译器可以轻松查看您传递的参数,使用它们来修饰函数名称,并生成正确的符号。例如,

int a, b; float f; char c;
foo(a,f,c) ; // compiler looks for an internal symbol called func_foo_int_float_char
foo(a,b,c) ; // compiler looks for a symbol called func_foo_int_int_char

再次强调,所有操作都是在编译时完成的。


7
正好相反,编译器首先查看所有的 "foos",选择符合标准中所有规则的一个,然后可能会为链接器生成一个重整名称。 - UncleBens

12

编译器可以查看函数调用,将其与已知的重载实现进行匹配,并选择正确的一个。无需动态表,在编译时静态完成所有操作。

更新:删除了我试图通过展示不同命名的函数来说明编译器可以在其中进行选择的尝试。


4
名称修饰只是为了区分标识符的名称,无论是否重载。名称修饰的主要目的不是为了适应重载,而是为了避免命名冲突。在重载的情况下,编译器必须确定调用哪个方法,这就是主要的逻辑所在。我认为 OP 想知道这是编译时还是运行时的事情。 - Aryabhatta
这并不准确,通常需要链接器计算调用地址。名称修饰非常重要。是的,如果函数恰好位于同一翻译单元中,则有省略链接请求的机会。是否实际发生取决于具体实现细节。 - Hans Passant
@unwindпјҡеҰӮжһңжҲ‘и°ғз”Ёfoo(1.0)дјҡеҸ‘з”ҹд»Җд№Ҳпјҹзј–иҜ‘еҷЁдјҡеҜ»жүҫдёҚеӯҳеңЁзҡ„void fo_double(double z)гҖӮжӯЈеҰӮе…¶д»–дәәжүҖиҜҙпјҢеҗҚз§°ж··ж·ҶдёҚжҳҜзӯ”жЎҲгҖӮ - jalf
我认为这是一种教学上的近似真相,我感到很舒适。 - Crashworks
1
@nobugz: 编译器可以确定调用哪个方法并生成正确的函数名称(无论是否被混淆),然后链接器使用该名称。您正在说我所说的一件事。混淆的名称只是避免在链接时发生冲突的解决方案之一。提供函数重载不需要它们。函数重载特性可能会改变名称混淆的方式,但这只是拥有唯一名称的必然结果。 - Aryabhatta
显示剩余3条评论

7
如果你在谈论同一类的重载方法,例如:
void function(int n);
void function(char *s);
...

objectInstance->function("Hello World")  

这是一个编译时的东西。编译器在此时知道(或在某些情况下,做出最佳猜测)应调用哪个方法。

我在问题中发表的评论,在此重复一遍。

那些建议使用名称混淆的人我认为是误导了。并不是编译器混淆名称然后只在混淆名称中进行查找。它需要从可用的方法中推断出正确的类型。一旦它完成了这个过程,就已经知道应该调用哪个方法了。然后,它将混淆的名称作为最后一步。名称混淆不是确定要调用哪个重载函数的先决条件。


给那个给了-1的人。至少留下一个评论。如果信息不准确,我可以删除/编辑帖子。也给我一个学习的机会! - Aryabhatta
我猜那个人(不是我)给你的回答投了反对票,因为你并没有真正给出一个清晰的答案。相反,你只是说“编译时”和“编译器知道该怎么做”,这确实有点不准确。 - AndiDog
好的,名称混淆在我看来并不准确。我们能说的最好的是“编译器知道”。每个编译器可能都有自己的实现。 - Aryabhatta
名称修饰有助于(就我所知,是必需的)链接器。 - ChrisW
你也可以将你的评论复制到unwind中,因为你展示了一种有趣的名称混淆用法。 - yeyeyerman

3

重载函数在编译时被解析。编译器会为给定的参数集找到适合的匹配,并通过其地址调用相应的函数(void foo(int)void foo() 实际上是两个完全独立的函数 - 如果在代码中有 foo(4),编译器知道要调用哪个函数)。


0
C++ 编译器使用名称修饰(针对每个重载函数使用不同的名称)来区分目标文件中的函数。例如:
int test(int a){}
int test(float a,float b){}
int test(double a){}
int testbam(double a){}

将产生符号名称__Z4testi__Z4testff__Z4testd__Z7testbamd。这种名称修饰高度依赖于编译器(可悲的是),也是为什么经常优先使用C而不是C++的原因之一。

在调用函数test时,编译器会将给定的参数类型和参数数量与每个函数重载进行匹配。然后使用函数原型来确定应该调用哪个函数。


0

我相信这是通过名称重整实现的:

你所知道的名为foo(int)和foo(double)的函数实际上被命名为int_foo()和double_foo()(或类似的名称,我不完全确定C++使用的特定语义)。这意味着C++符号通常比代码中给出的名称大一个数量级。


-1

函数签名由函数名称+参数类型组成


-1

即使没有函数重载,编译器通常也会混淆函数和变量名称。这被称为名称修饰。它发生在C和C++中。函数名可以通过以下方式进行装饰:(1)调用约定,(2)C++函数重载,(3)类成员函数。

GNU binutil c++filt可以取消装饰这个混淆的名称,在Windows中,有UnDecorateSymbolName


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接