【问题标题】:How do C/C++/Objective-C compare with C# when it comes to using libraries?在使用库时,C/C++/Objective-C 与 C# 相比如何?
【发布时间】:2009-12-17 09:42:19
【问题描述】:

此问题基于上一个问题:How does C# compilation get around needing header files?

确认 C# 编译使用多次传递基本上回答了我最初的问题。此外,答案表明 C# 使用存储在程序集中的类型和方法签名元数据在编译时检查代码语法。

问:C/C++/Objective-C 如何知道在运行时加载哪些在编译时链接的代码?并且要将其与我熟悉的技术联系起来,C#/CLR 是如何做到这一点的?

如果我错了,请纠正我,但是对于 C#/CLR,我直观的理解是在执行时会检查某些路径是否有程序集,并且基本上所有代码都是在运行时动态加载和链接的。

编辑:更新为包含 C++ 和 Objective-C 与 C。

更新:澄清一下,我真正好奇的是 C/C++/Objective-C 编译如何将我的源代码中的“外部定义”符号与该代码的实际实现相匹配,什么是编译输出,基本上是微处理器如何执行编译输出以将控制无缝传递给库代码(就指令指针而言)。我已经用 CLR 虚拟机完成了这项工作,但我很想知道在实际微处理器上的 C++/Objective-C 中它在概念上是如何工作的。

【问题讨论】:

    标签: c# c compiler-construction clr


    【解决方案1】:

    链接器在 C/C++ 构建中起着至关重要的作用,以解决外部依赖关系。 .NET 语言不使用链接器。

    有两种外部依赖项,它们的实现在链接时可以在另一个 .obj 或 .lib 文件中作为链接器的输入提供。以及在另一个可执行模块中可用的那些。 Windows 中的 DLL。

    链接器在链接时解析第一个,没有什么复杂的事情发生,因为链接器会知道依赖的地址。后一步高度依赖于平台。在 Windows 上,必须为链接器提供一个导入库。一个非常简单的文件,它仅声明 DLL 的名称和 DLL 中导出的定义列表。链接器通过在代码中输入跳转并在外部依赖表中添加一条指示跳转位置的记录来解决依赖关系,以便可以在运行时对其进行修补。 DLL 的加载和导入表的设置由 Windows 加载程序在运行时完成。这是过程的鸟瞰图,有很多无聊的细节可以让这个过程尽快发生。

    在托管代码中,所有这些都是在运行时完成的,由 JIT 编译器驱动。它将 IL 转换为机器代码,由程序执行驱动。每当执行引用另一种类型的代码时,JIT 编译器就会开始行动,加载该类型并翻译该类型的调用方法。加载类型的副作用是加载包含该类型的程序集,如果之前没有加载的话。

    值得注意的是构建时可用的外部依赖项的差异。 C/C++ 编译器一次编译一个源文件,依赖关系由链接器解析。托管编译器通常将所有创建程序集的源文件作为输入,而不是一次编译一个。实际上支持单独的编译和链接(.netmodule 和 al.exe),但可用工具并不能很好地支持,因此很少这样做。此外,它不支持扩展方法和部分类等功能。因此,托管编译器需要更多的系统资源来完成工作。在现代硬件上随时可用。 C/C++ 的构建过程是在没有这些资源的时代建立的。

    【讨论】:

    • 太棒了,谢谢(接受包括 CLR 比较)。
    • 酷。不知道 .netmodule。
    • 一个很好的答案。不过,我想说一件事。您在谈论 .obj 文件。好吧,实际上您的意思是引用目标文件,编译器/链接器组合可以随意调用它们。 GCC(这是迄今为止这些语言最杰出的编译器)称它们为 .o 文件。
    【解决方案2】:

    我相信您所询问的过程是一种称为符号解析的过程。在常见的情况下,它按照以下方式工作(我试图保持它与操作系统无关):

    1. 第一步是编译各个源文件以创建目标文件。源代码变成了机器语言指令,任何未在源文件本身中定义的符号(即函数或外部变量名称)都会导致占位符留在编译的机器语言代码中,无论它们被引用到哪里。未知符号也被添加到目标文件中的列表中 - 在编译结束时,此列表包含目标文件中的每个未解析符号,与所有目标文件中的位置交叉引用添加的占位符。每个目标文件还包含由该目标文件导出的符号列表 - 即,它希望在该目标文件之外的代码中定义在该目标文件中的符号 - 以及值那些符号。

    2. 第二步是静态链接。这也发生在编译时。在静态链接过程中,第一步中创建的所有目标文件和任何静态库文件(它们只是一种特殊的目标文件)都被组合成一个可执行文件。静态链接器对每个目标文件和静态库导出的符号进行传递,它被告知要链接在一起,并构建导出符号(及其值)的完整列表。然后,它会遍历每个目标文件中的未解析符号,并在主列表中找到该符号的位置,将所有占位符替换为该符号的实际值。对于在此过程结束时仍未解析的任何符号,链接器会查看它所知道的所有动态库导出的符号列表。它构建所需的动态库列表,并将其存储在可执行文件中。如果仍未找到任何符号,则链接过程失败。

    3. 第三步是动态链接,它发生在运行时。动态链接器加载可执行文件中包含的列表中的动态库,并将剩余未解析符号的占位符替换为来自动态库的相应值。这可以“急切地”完成 - 在可执行文件加载之后但在它运行之前 - 或“懒惰地”,这是按需的,当第一次访问未解析的符号时。

    【讨论】:

      【解决方案3】:

      C 和 C++ 标准对运行时加载没有任何规定 - 这完全是特定于操作系统的。在 Windows 的情况下,将代码与包含函数名称和它们所在的 DLL 名称的导出库(在创建 DLL 时生成)链接。链接器在包含此信息的代码中创建存根。在运行时,C/C++ 运行时使用这些存根与 Windows LoadLibrary() 和相关函数一起将函数代码加载到内存中并执行它。

      【讨论】:

      • 澄清一下,我真正好奇的是 C/C++/ObjC 程序如何将一个可执行文件或库中的“外部定义”符号与该代码的实际实现相匹配,以及如何比较使用 C#/CLR。自从我用 C/C++/ObjC 编译任何东西以来已经有一段时间了。所以你是说对于 C/C++,但听起来所有依赖库代码在编译时都链接在一起,我们最终得到一个单一的可执行文件?但是 Windows 编译做了一些特殊的事情来实现它的动态链接......
      • 我们最终得到一个可执行文件,但一些可执行代码保留在单独的 DLL 中。是的,Windows 编译/链接过程中有“特殊的东西”,就像(例如)Linux 编译/链接过程中一样。编程语言标准没有规定这种特殊的东西。
      【解决方案4】:

      您指的是 DLL 库对吗?

      操作系统有一定的模式来寻找所需的文件(通常从应用程序本地路径开始,然后继续到环境变量指定的文件夹。)

      【讨论】:

      • 不一定。我可能使用了不正确的术语。我主要是一名 C# 开发人员,由于 CLR,其中很多内容都被抽象出来了。 “库”是指预先存在的代码(在 C/C++/ObjC 中是预编译的目标代码;在 CLR 中是预编译的字节码)。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多