显然(至少根据gcc -std=c99
)C99 不支持函数重载。不支持 C 中的某些新特性的原因通常是向后兼容性,但在这种情况下,我想不出函数重载会破坏向后兼容性的单一情况。不包括此基本功能的原因是什么?
3 回答
要了解为什么您不太可能在 C 中看到重载,可能有助于更好地了解 C++ 如何处理重载。
在编译代码之后,但在它准备好运行之前,必须链接中间目标代码。这会将已编译函数和其他对象的粗略数据库转换为准备好加载/运行的二进制文件。这个额外的步骤很重要,因为它是可用于编译程序的模块化原则机制。此步骤允许您从现有库中获取代码并将其与您自己的应用程序逻辑混合。
在这个阶段,目标代码可能已经用任何语言编写,具有任何特征组合。为了使这成为可能,有必要制定某种约定,以便链接器能够在另一个对象引用它时选择正确的对象。如果您使用汇编语言进行编码,则在定义标签时,将准确使用该标签,因为假定您知道自己在做什么。
在 C 中,函数成为链接器的符号名称,因此当您编写
int main(int argc, char **argv) { return 1; }
编译器提供了一个目标代码档案,其中包含一个名为main
.
这很好用,但这意味着您不能有两个具有相同名称的对象,因为链接器将无法决定它应该使用哪个名称。链接器对参数类型一无所知,对一般代码也知之甚少。
C++ 通过将附加信息直接编码到符号名称中来解决此问题。返回类型以及参数的数量和类型被添加到符号名称中,并在函数调用时以这种方式引用。链接器甚至不必知道这种情况正在发生,因为据它所知,函数调用是明确的。
这样做的缺点是符号名称看起来不像原始函数名称。特别是,几乎不可能预测重载函数的名称以便您可以链接到它。要链接到外部代码,您可以使用extern "C"
,这会使这些函数遵循符号名称的 C 风格,但当然您不能重载这样的函数。
这些差异与每种语言的设计目标有关。C 面向可移植性和互操作性。C 不遗余力地做可预测和兼容的事情。C++ 更倾向于构建丰富而强大的系统,而不是非常关注与其他语言的交互。
我认为 C 不太可能追求任何会产生与 C++ 一样难以交互的代码的特性。
编辑: Imagist问:
如果您将 int main(int argc, char** argv) 解析为 main-int-int-char** 而不是 main(这是标准)?我认为这里没有问题。事实上,在我看来,这为您提供了更多信息(可用于优化等)
为了回答这个问题,我将再次转向 C++ 以及它处理重载的方式。C++ 使用这种机制,几乎与描述的完全一样,但有一个警告。C++ 并没有标准化其自身的某些部分应该如何实现,然后继续建议这种遗漏的一些后果。特别是,C++ 具有丰富的类型系统,其中包括虚拟类成员。这个特性应该如何实现留给编译器编写者,vtable 解析的细节对函数签名有很大的影响。出于这个原因,C++ 故意建议编译器编写者使名称修饰在编译器之间或具有这些关键特性的不同实现的相同编译器之间相互不兼容。
这只是更深层次问题的一个症状,即虽然 C++ 和 C 等高级语言具有详细的类型系统,但低级机器代码完全是无类型的。任意丰富的类型系统建立在机器级别提供的无类型二进制文件之上。链接器无法访问高级语言可用的丰富类型信息。链接器完全依赖编译器来处理所有类型抽象并生成正确的无类型目标代码。
C++ 通过在损坏的对象名称中编码所有必要的类型信息来做到这一点。然而,C 有一个明显不同的重点,旨在成为一种可移植的汇编语言。因此,C 更喜欢在声明的名称和结果对象的符号名称之间具有严格的一一对应关系。如果 C 修改了它的名称,即使是以标准化和可预测的方式,您也必须付出巨大努力将更改后的名称与所需的符号名称相匹配,否则您将不得不像在 c++ 中那样将其关闭。这种额外的努力几乎没有任何好处,因为与 C++ 不同,C 的类型系统相当小且简单。
同时,定义几个名称相似的 C 函数实际上是一种标准做法,这些函数仅根据它们作为参数的类型而有所不同。有关这方面的冗长示例,请查看OpenGL 命名空间。
编译 C 源代码时,符号名称将保持不变。如果您引入函数重载,您应该提供一种名称修饰技术来防止名称冲突。因此,与 C++ 一样,您将在编译后的二进制文件中拥有机器生成的符号名称。
此外,C 没有严格的类型。在 C 中,很多东西都可以隐式转换。重载解析规则的复杂性可能会在这种语言中引入混乱。
包括我在内的许多语言设计者都认为函数重载与 C 的隐式提升相结合会导致代码难以理解。作为证据,请查看积累的有关 C++ 的知识体系。
总的来说,C99 旨在成为一个与现有实践大体兼容的适度修订。超载将是一个很大的偏离。