【问题标题】:Unclear on linking vs compilation不清楚链接与编译
【发布时间】:2021-12-18 11:07:57
【问题描述】:

我知道存在许多解决同一问题的问题,但我一直无法找到一个可以回答我的问题的问题。我理解编译和链接之间的大区别,前者将每个源文件以目标文件的形式翻译成机器代码,而后者将这些目标文件“链接”在一起形成一个可执行文件。

但是,一旦我们将预处理投入到这种混合中,我就会感到困惑。我的理解是,如果我们导入另一个库,那么这个库的 all 本质上就是复制并粘贴到我们的代码中。因此,即使我们正在处理多个文件并在另一个文件中调用,内容是否仍被转储到另一个文件中?如果是这种情况(我确信这是不正确的,并且我在某处有误解)为什么链接甚至是必要的?我们不是已经在处理一个巨大的聚合源文件了吗?

【问题讨论】:

  • 链接将所有引用替换为正确的地址并生成输出(它将公开导出的函数,例如在动态库中)。它还将检查您的外部依赖项。而且它不是一个巨大的单个文件,需要链接多个转换单元
  • It is my understanding that if we import another library then all of this library is essentially copy and pasted into our code - 这是错误的。而且C中没有import这样的东西
  • @AnisBelaid 所以链接器只从导入的库中提取必要的函数并检查所有文件,因此声明中没有错误/冲突?
  • @APM500 是的,这是链接器所做的事情之一。
  • 编译是将一种语言(c、swift、...)翻译成另一种语言(通常是处理器代码)。而已。链接正在构建一个可执行文件,其中包含所需的所有不同的(处理器)代码块:链接器在给定的目标文件或库列表中查找需要哪些块。这也意味着您可以更改库或对象以使用不同的版本而无需编译。

标签: c++ c compilation linker


【解决方案1】:

预处理发生在编译之前。预处理器获取一个或多个 source 文件,并输出另一个 source 文件,然后对其进行编译。预处理器是一个文本到文本的转换器,它与链接无关。

从概念上讲,可以使用预处理器将所有内容转储到一个源文件中,然后直接将其编译为可执行文件,跳过生成目标文件并将它们链接在一起的阶段。然而,这在实践中会非常不方便。想象一个 100,000,000 行代码的程序(这包括所有标准库和所有平台库和所有第三方库)。您需要更改一行。你愿意再次编译所有 100,000,000 行吗?当你在那一行出错时,再做一次(一次又一次,一次又一次)?

一些 库完全作为头文件分发。它们不需要任何二进制文件,并且每次编译程序时都会与您的程序一起编译。但并不是所有的图书馆都是这样的。有些太大而无法每次编译。有些不是用 C 或 C++ 编写的(例如,它们需要一些汇编语言,或者可能是 Fortran)。有些不能作为源代码分发,因为供应商出于版权原因不愿意这样做。在所有这些情况下,解决方案是将库编译为目标文件,然后将这些目标文件与仅包含它们公开的函数和变量的接口(没有定义的声明)的标头一起分发。

您提到的<iostream> 是一个混合包。在大多数实现中,它既包含每次编译程序时编译的函数定义(模板和小型内联函数),也包含外部函数的声明,其定义由供应商编译并作为预编译库分发。

【讨论】:

    【解决方案2】:

    我的理解是,如果我们导入另一个库,那么所有这个库本质上都是复制并粘贴到我们的代码中。

    这通常是不正确的。

    在 C 和 C++ 中,#include 指令主要用于导入头文件,而不是所有库。头文件主要用于声明函数,有时是对象,而不定义它们。在通用语言中,头文件中的声明描述函数但不定义函数。

    例如,这是一个声明:

    double square(double x);
    

    上面写着“square 是一个接受 double 参数并返回 double 值的函数”,但它不包含 square 的代码,也没有告诉我们 square 做了什么。 (另外,参数名称x 可以在声明中省略。)声明只是告诉我们调用函数需要什么:我们必须将double 值放在适当的位置以传递参数,然后我们调用例程,我们期望返回一个double 值。编译器无法根据该声明为函数生成汇编代码。

    定义包含函数的代码:

    double square(double x)
    {
        return x*x;
    }
    

    在大多数库中,定义位于单独编译的源文件中。这些编译的结果可以以各种形式的“库”文件形式提供,例如.a.so.dylib 或其他文件。该库还提供了仅包含声明而不包含定义的头文件。

    使用该库的程序使用#include 来包含头文件。这为编译器提供了调用库例程所需的信息,但它不会导入库的all。定义保持独立,编译到上面提到的其他文件中。要制作一个完整的程序,程序的普通对象模块必须与库的库文件相链接。

    【讨论】:

    • 所以头文件只是告诉编译器这些函数存在并分配正确的内存量,而预处理器是处理这个的。链接器是从您的源代码和您包含的库(具有实际的函数实现)中获取 obj 文件并将它们放在一起的东西?对吗?
    • @APM500:不是头文件(或任何地方)中定义的声明不会告诉编译器要分配多少内存。函数声明只是告诉编译器该函数存在于某处以及如何调用它。编译器不需要知道调用它的函数有多大。并且,如果头文件中有对象声明,它们可能会揭示对象有多大(因为完整的类型显示它的大小),但编译器仍然不会因为声明而保留内存;它只知道该对象在某个地方。否则,是的,链接器将它们放在一起。
    猜你喜欢
    • 1970-01-01
    • 2021-09-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-08
    • 2020-02-17
    • 1970-01-01
    相关资源
    最近更新 更多