【问题标题】:In C++ how is function overloading typically implemented?在 C++ 中,函数重载通常是如何实现的?
【发布时间】:2010-02-09 07:46:51
【问题描述】:

如果没有函数重载,函数名作为函数代码的地址,当函数被调用时,使用函数名很容易找到它的地址。然而,通过函数重载,程序如何准确地找到正确的函数地址?是否有一个类似于虚拟表的隐藏表来存储重载函数及其地址?非常感谢!

【问题讨论】:

  • 我认为建议改名的人被误导了。编译器并没有破坏名称,只是在被破坏的名称中进行查找。它需要从可用的方法中推断出正确的类型。一旦这样做,它就已经知道要调用哪个方法。然后,它使用经过修改的名称作为 last 步骤。名称修改不是确定调用哪个重载函数的先决条件。
  • 也许这条评论应该出现在您所指的错误答案之一中?
  • 太多了,就放在这里了。
  • 完全正确。给定void foo(int);void foo(std::string);foo(1.0f) 将调用第一个。 “名称修改”建议会让编译器查找 ? foo(float) 并失败。
  • 如果您想要重载函数的地址,有时您必须将该地址转换为适当的类型。假设你有两个函数 foo,一个是 int,一个是 float,你想要一个 int 的地址,你写static_cast<void(*)(int)>(&foo)

标签: c++ function overloading


【解决方案1】:

Name mangling

这一切都在编译时完成。 C++ 编译器实际上会在内部修改你给它的函数名,以便像

这样的函数
int foo(int a, float b, char c) 

在内部获得一个等同于

的名称
func_foo_int_float_char()

(真正的符号通常是像?CFoo@Foo@@QAAX_N@Z 这样的gobbledygook)。

如您所见,名称的修饰取决于传递的参数的确切数量和类型。因此,当您调用一个函数时,编译器很容易查看您传递的参数,用它们修饰函数名称,并得出正确的符号。例如,

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,根据标准中的所有规则选择一个匹配的,然后可能会为链接器发出一个错误的名称。
【解决方案2】:

编译器可以查看调用,并将其与已知的现有重载实现进行匹配,然后选择正确的实现。不需要动态表,在编译时完全可以静态完成。

更新:取消了我通过显示编译器可以选择的不同命名函数来说明概念的尝试。

【讨论】:

  • 人们说这个技术名字Mangling。搜索此关键字。
  • 名称修饰只是为了区分标识符的名称,是否重载。名称修饰的主要目的不是迎合重载,而是避免命名冲突。在重载的情况下,编译器必须确定调用哪个方法,这就是主要逻辑所在。我相信 OP 想知道这是否是编译时/运行时的事情。
  • 这不准确,通常是链接器需要计算调用地址。名字装饰很重要。是的,如果函数恰好位于同一个翻译单元中,则有机会忽略链接请求。这是否真的发生是一个实现细节。
  • @unwind:如果我打电话给foo(1.0),会发生什么?编译器查找不存在的void fo_double(double z)。正如其他人所说,名称修改不是答案。
  • @nobugz:编译器可以确定调用哪个方法并生成链接器随后使用的正确函数名(无论是否被修改)。你说的和我说的一样。损坏的名称只是在链接期间避免冲突的解决方案之一。它们不是提供函数重载所必需的。函数重载特性可能会改变名称的错位方式,但这只是具有唯一名称的必然结果。
【解决方案3】:

如果你说的是同一个类的重载方法,像这样:

void function(int n);
void function(char *s);
...

objectInstance->function("Hello World")  

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

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

我认为建议改名的人被误导了。编译器并没有破坏名称,只是在被破坏的名称中进行查找。它需要从可用的方法中推断出正确的类型。一旦这样做,它就已经知道要调用哪个方法。然后,它使用修改后的名称作为 last 步骤。名称修改不是确定调用哪个重载函数的先决条件。

【讨论】:

  • 给给-1的人。至少留下评论。如果信息不准确,我可以删除/编辑帖子。也给我一个学习的机会!
  • 我猜那个人(不是我)投了反对票,因为你没有真正给出明确的答案。相反,你只是说“编译时间”和“编译器只知道该做什么”,这确实有点不准确。
  • 好吧,名称修改是不准确的,IMO。我们能说的最好的就是“编译器知道”。每个编译器都可能有自己的实现。
  • 名称修饰有助于(据我所知,需要)链接器。
  • 您还可以复制您的评论以放松,因为您公开了有趣的名称修饰的不同用法。
【解决方案4】:

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

【讨论】:

    【解决方案5】:

    我相信,这是通过名称修改实现的:

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

    【讨论】:

      【解决方案6】:

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

      【讨论】:

        【解决方案7】:

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

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

        【讨论】:

          【解决方案8】:

          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 时,编译器会根据每个函数重载匹配给定的参数类型和参数数量。然后使用函数原型来找出应该调用哪一个。

          【讨论】:

            猜你喜欢
            • 2010-10-03
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-03-19
            • 2017-03-09
            相关资源
            最近更新 更多