如果没有函数重载,函数名就充当了函数代码的地址。在调用函数时,可以使用其名称轻松找到地址。然而,如果有函数重载,程序怎么准确地找到正确的函数地址呢?是否有一个类似于虚表的隐藏表存储了重载函数及其地址?非常感谢!
如果没有函数重载,函数名就充当了函数代码的地址。在调用函数时,可以使用其名称轻松找到地址。然而,如果有函数重载,程序怎么准确地找到正确的函数地址呢?是否有一个类似于虚表的隐藏表存储了重载函数及其地址?非常感谢!
名称修饰是指在编译时完成的。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
再次强调,所有操作都是在编译时完成的。
编译器可以查看函数调用,将其与已知的重载实现进行匹配,并选择正确的一个。无需动态表,在编译时静态完成所有操作。
更新:删除了我试图通过展示不同命名的函数来说明编译器可以在其中进行选择的尝试。
foo(1.0)
дјҡеҸ‘з”ҹд»Җд№Ҳпјҹзј–иҜ‘еҷЁдјҡеҜ»жүҫдёҚеӯҳеңЁзҡ„void fo_double(double z)
гҖӮжӯЈеҰӮе…¶д»–дәәжүҖиҜҙпјҢеҗҚз§°ж··ж·ҶдёҚжҳҜзӯ”жЎҲгҖӮ - jalfvoid function(int n);
void function(char *s);
...
objectInstance->function("Hello World")
这是一个编译时的东西。编译器在此时知道(或在某些情况下,做出最佳猜测)应调用哪个方法。
我在问题中发表的评论,在此重复一遍。
那些建议使用名称混淆的人我认为是误导了。并不是编译器混淆名称然后只在混淆名称中进行查找。它需要从可用的方法中推断出正确的类型。一旦它完成了这个过程,就已经知道应该调用哪个方法了。然后,它将混淆的名称作为最后一步。名称混淆不是确定要调用哪个重载函数的先决条件。
重载函数在编译时被解析。编译器会为给定的参数集找到适合的匹配,并通过其地址调用相应的函数(void foo(int)
和 void foo()
实际上是两个完全独立的函数 - 如果在代码中有 foo(4)
,编译器知道要调用哪个函数)。
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
时,编译器会将给定的参数类型和参数数量与每个函数重载进行匹配。然后使用函数原型来确定应该调用哪个函数。
我相信这是通过名称重整实现的:
你所知道的名为foo(int)和foo(double)的函数实际上被命名为int_foo()和double_foo()(或类似的名称,我不完全确定C++使用的特定语义)。这意味着C++符号通常比代码中给出的名称大一个数量级。
函数签名由函数名称+参数类型组成
即使没有函数重载,编译器通常也会混淆函数和变量名称。这被称为名称修饰。它发生在C和C++中。函数名可以通过以下方式进行装饰:(1)调用约定,(2)C++函数重载,(3)类成员函数。
GNU binutil c++filt
可以取消装饰这个混淆的名称,在Windows中,有UnDecorateSymbolName。
void foo(int);
和void foo(std::string);
,foo(1.0f)
将调用第一个函数。如果按照“名称修饰”的建议,编译器将寻找? foo(float)
并失败。 - MSaltersstatic_cast<void(*)(int)>(&foo)
。 - fredoverflow