解决方法
编译代码之后,但在准备运行之前,必须链接中间对象代码.这将编译函数和其他对象的粗略数据库转换为准备加载/运行二进制文件.这一额外步骤很重要,因为它是编译程序可用的模块化的主要机制.此步骤允许您从现有库中获取代码并将其与您自己的应用程序逻辑混合.
在此阶段,目标代码可能已使用任何语言编写,并具有任何功能组合.为了实现这一点,有必要采用某种约定,以便链接器能够在另一个对象引用它时选择正确的对象.如果您使用汇编语言进行编码,那么在定义标签时,会严格使用该标签,因为假设您知道自己在做什么.
int main(int argc,char **argv) { return 1; }
编译器提供目标代码存档,其中包含一个名为main的对象.
这很好用,但这意味着你不能拥有两个具有相同名称的对象,因为链接器将无法决定它应该使用哪个名称.链接器对参数类型一无所知,而且通常对代码知之甚少.
C通过直接将符号名称中的附加信息编码来解决此问题.返回类型以及参数的数量和类型将添加到符号名称中,并在函数调用时以这种方式引用.链接器不必知道这甚至发生了,因为据它所知,函数调用是明确的.
这样做的缺点是符号名称看起来与原始函数名称不同.特别是,几乎不可能预测重载函数的名称是什么,以便您可以链接到它.要链接到foriegn代码,你可以使用extern“C”,这会导致这些函数遵循C风格的符号名称,但当然你不能重载这样的函数.
这些差异与每种语言的设计目标有关. C面向可移植性和互操作性. C不遗余力地做出可预测和兼容的事情. C更倾向于构建丰富而强大的系统,而不是非常注重与其他语言的交互.
编辑:Imagist问:
Would it really be less portable or
more difficult to interact with a
function if you resolved int main(int
argc,char** argv) to something like
main-int-int-char** instead of to main
(and this were part of the standard)?
I don’t see a problem here. In fact,
it seems to me that this gives you
more information (which could be used
for optimization and the like)
要回答这个问题,我将再次讨论C及其处理重载的方式. C使用这种机制,几乎完全如上所述,但有一点需要注意. C没有标准化应该如何实现自身的某些部分,然后继续建议如何忽略这些遗漏的后果.特别是,C有一个富类型系统,包括虚拟类成员.应如何实现此功能由编译器编写者完成,vtable解析的详细信息对函数签名有很大影响.出于这个原因,C故意建议编译器编写者使编译名称在编译器或具有这些关键特性的不同实现的相同编译器之间是互不兼容的.
这只是一个更深层次问题的症状,虽然像C和C这样的高级语言具有详细的类型系统,但是较低级别的机器代码完全没有类型.任意丰富的类型系统建立在机器级别提供的无类型二进制文件之上.链接器无法访问更高级语言可用的丰富类型信息.链接器完全依赖于编译器来处理所有类型的抽象并生成正确的无类型对象代码.
C通过编码受损对象名称中的所有必要类型信息来完成此操作.然而,C具有明显不同的焦点,旨在成为一种可移植的汇编语言.因此,C优选在声明的名称和结果对象的符号名称之间具有严格的一对一对应关系.如果C篡改了它的名字,即使是以标准化和可预测的方式,你也必须努力将改变的名称与所需的符号名称相匹配,否则你将不得不像在c中那样将其关闭.这种额外的努力几乎没有任何好处,因为与C不同,C的类型系统相当小而且简单.
同时,定义几个类似命名的C函数实际上是一种标准做法,这些函数仅根据它们作为参数的类型而变化.对于这方面的长篇例子,请看一下OpenGL namespace.