链接与编译不清楚
Unclear on linking vs compilation
我知道存在许多解决同一问题的问题,但我一直找不到能回答我问题的问题。我了解编译和链接之间的主要区别,前者将每个源文件以目标文件的形式转换为机器代码,而后者将这些目标文件“链接”到一个可执行文件中。
但是,一旦我们将预处理投入到这种混合中,我就会感到困惑。据我了解,如果我们导入另一个库,那么这个库的 all 本质上是复制并粘贴到我们的代码中。因此,即使我们正在处理多个文件并在另一个文件中调用一个文件,内容是否仍会转储到另一个文件中?如果是这种情况(我确定这是不正确的并且我在某处有误解)为什么甚至需要链接?我们不是已经在处理一个巨大的聚合源文件了吗?
预处理发生在编译之前。预处理器获取一个或多个 source 文件,并输出另一个 source 文件,然后对其进行编译。预处理器是一个text-to-text转换器,它与链接无关。
从概念上讲,可以使用预处理器将所有内容转储到一个源文件中,然后将其直接编译为可执行文件,跳过生成 object 文件并将它们链接在一起的阶段。然而,这在实践中非常不方便。想象一个 100,000,000 行代码程序(这包括所有标准库和所有平台库以及所有 third-party 库)。你需要换一根线。你愿意把一亿行都编译一遍吗?当你在那一行出错时,再做一遍(一遍又一遍又一遍又一遍)?
一些 库完全作为header 文件分发。它们不需要任何二进制文件,每次编译程序时都会与您的程序一起编译。但并不是所有的图书馆都是这样。有些太大而无法每次编译。有些不是用 C 或 C++ 编写的(例如,它们需要一些汇编语言,或者可能是 Fortran)。有些不能作为源代码分发,因为供应商出于版权原因不愿意这样做。在所有这些情况下,解决方案是将库编译为 object 文件,然后将这些 object 文件与仅包含 接口 [=24] 的 header 一起分发=](没有定义的声明)它们公开的函数和变量。
<iostream>
你说的是鱼龙混杂。在大多数实现中,它既包含每次编译程序时编译的函数定义(模板和小型内联函数),也包含外部函数的声明,其定义由供应商编译并作为预编译库分发。
It is my understanding that if we import another library then all of this library is essentially copy and pasted into our code.
这通常是不正确的。
在 C 和 C++ 中,#include
指令大多只用于导入头文件,而不是所有的库。头文件主要用于声明函数,有时是对象,而不定义它们。在通用语言中,头文件中的声明描述了函数,但不定义函数。
例如,这是一个声明:
double square(double x);
上面写着“square
是一个接受 double
参数和 returns 一个 double
值的函数”,但它不包含 square
并没有告诉我们 square
做什么。 (此外,参数名称 x
可以在声明中省略。)声明只是告诉我们需要调用函数的内容:我们必须将 double
值放在适当的位置以传递一个参数,然后我们调用例程,我们期望返回一个 double
值。编译器无法从此声明生成函数的汇编代码。
定义包含函数的代码:
double square(double x)
{
return x*x;
}
在大多数库中,定义都在单独编译的源文件中。这些编译的结果可以各种形式的“库”文件提供,例如 .a
、.so
、.dylib
或其他文件。该库还提供仅包含声明而不包含定义的头文件。
使用该库的程序使用 #include
来包含头文件。这为编译器提供了调用库例程所需的信息,但它不会导入库的 all。定义保持独立,编译到上面提到的其他文件中。要制作一个完整的程序,程序的普通目标模块必须与库的库文件链接起来。
我知道存在许多解决同一问题的问题,但我一直找不到能回答我问题的问题。我了解编译和链接之间的主要区别,前者将每个源文件以目标文件的形式转换为机器代码,而后者将这些目标文件“链接”到一个可执行文件中。
但是,一旦我们将预处理投入到这种混合中,我就会感到困惑。据我了解,如果我们导入另一个库,那么这个库的 all 本质上是复制并粘贴到我们的代码中。因此,即使我们正在处理多个文件并在另一个文件中调用一个文件,内容是否仍会转储到另一个文件中?如果是这种情况(我确定这是不正确的并且我在某处有误解)为什么甚至需要链接?我们不是已经在处理一个巨大的聚合源文件了吗?
预处理发生在编译之前。预处理器获取一个或多个 source 文件,并输出另一个 source 文件,然后对其进行编译。预处理器是一个text-to-text转换器,它与链接无关。
从概念上讲,可以使用预处理器将所有内容转储到一个源文件中,然后将其直接编译为可执行文件,跳过生成 object 文件并将它们链接在一起的阶段。然而,这在实践中非常不方便。想象一个 100,000,000 行代码程序(这包括所有标准库和所有平台库以及所有 third-party 库)。你需要换一根线。你愿意把一亿行都编译一遍吗?当你在那一行出错时,再做一遍(一遍又一遍又一遍又一遍)?
一些 库完全作为header 文件分发。它们不需要任何二进制文件,每次编译程序时都会与您的程序一起编译。但并不是所有的图书馆都是这样。有些太大而无法每次编译。有些不是用 C 或 C++ 编写的(例如,它们需要一些汇编语言,或者可能是 Fortran)。有些不能作为源代码分发,因为供应商出于版权原因不愿意这样做。在所有这些情况下,解决方案是将库编译为 object 文件,然后将这些 object 文件与仅包含 接口 [=24] 的 header 一起分发=](没有定义的声明)它们公开的函数和变量。
<iostream>
你说的是鱼龙混杂。在大多数实现中,它既包含每次编译程序时编译的函数定义(模板和小型内联函数),也包含外部函数的声明,其定义由供应商编译并作为预编译库分发。
It is my understanding that if we import another library then all of this library is essentially copy and pasted into our code.
这通常是不正确的。
在 C 和 C++ 中,#include
指令大多只用于导入头文件,而不是所有的库。头文件主要用于声明函数,有时是对象,而不定义它们。在通用语言中,头文件中的声明描述了函数,但不定义函数。
例如,这是一个声明:
double square(double x);
上面写着“square
是一个接受 double
参数和 returns 一个 double
值的函数”,但它不包含 square
并没有告诉我们 square
做什么。 (此外,参数名称 x
可以在声明中省略。)声明只是告诉我们需要调用函数的内容:我们必须将 double
值放在适当的位置以传递一个参数,然后我们调用例程,我们期望返回一个 double
值。编译器无法从此声明生成函数的汇编代码。
定义包含函数的代码:
double square(double x)
{
return x*x;
}
在大多数库中,定义都在单独编译的源文件中。这些编译的结果可以各种形式的“库”文件提供,例如 .a
、.so
、.dylib
或其他文件。该库还提供仅包含声明而不包含定义的头文件。
使用该库的程序使用 #include
来包含头文件。这为编译器提供了调用库例程所需的信息,但它不会导入库的 all。定义保持独立,编译到上面提到的其他文件中。要制作一个完整的程序,程序的普通目标模块必须与库的库文件链接起来。