有什么区别 – 1)预处理器,链接器,2)头文件,库? 我的理解是否正确?

好的,直到今天早上我才对这些术语感到困惑。 我想我有所不同,希望如此。

首先,令人困惑的是,由于预处理器已将头文件包含在包含函数的代码中,因此链接器链接到汇编器/编译器生成的目标文件的库函数是什么? 部分混淆主要是由于我对头文件和库之间的差异的无知而产生的。

经过一段谷歌搜索和堆栈溢出(就是术语?:p),我收集到头文件主要包含函数声明,而实际的实现是在另一个称为库的二进制文件中(我仍然不是100%确定这个)。

所以,假设在以下程序中: –

#include int main() { printf("whatever"); return 0; } 

预处理器在代码中包含头文件的内容。 编译器/编译器+汇编器完成其工作,然后最终链接器将此目标文件与另一个实际存储了printf()工作方式的目标文件组合在一起。

我的理解是否正确? 我可能会离开……你能帮我吗?

编辑:我一直想知道C ++ STL。 它总是让我困惑的是它究竟是什么,所有这些标题的集合或什么? 在阅读完回复之后,我可以说STL是一个目标文件/类似于目标文件的东西吗?

而且,我想在哪里可以读取函数的函数定义,如pow()sqrt()等等。我会打开头文件,但没有找到任何东西。 那么,库中的函数定义是否是二进制不可读的forms?

AC源文件经历两个主要阶段,(1)预处理器阶段,其中C源代码由预处理器实用程序处理,其寻找预处理器指令并执行那些动作;以及(2)编译阶段,其中处理的C源代码是实际编译生成目标代码文件。

预处理器是一个执行文本操作的实用程序。 它将包含文本(通常为C源代码)的文件作为输入,该文件可能包含预处理程序指令,并通过应用找到文本输入的任何指令来输出文件的修改版本以生成文本输出。

该文件不必是C源代码,因为预处理器正在进行文本操作。 我已经看到C Preprocssor用于扩展make实用程序,允许将preprossor指令包含在make文件中。 带有C预处理程序指令的make文件通过C预处理程序实用程序运行,然后生成的输出结果输入make以执行make目标的实际构建。

图书馆和链接

库是包含各种function的目标代码的文件。 这是一种在将多个源文件编译成单个文件时将其输出打包的方法。 很多时候提供库文件以及头文件(包含文件),通常具有.h文件扩展名。 头文件包含函数声明,全局变量声明以及库所需的预处理程序指令。 因此,要使用该库,您需要包含使用#include指令提供的头文件,并链接到库文件。

库文件的一个很好的特性是您提供源代码的编译版本而不是源代码本身。 另一方面,由于库文件包含已编译的源代码,因此用于生成库文件的编译器必须与用于编译自己的源代码文件的编译器兼容。

通常使用两种类型的库。 第一个和更旧的类型是静态库。 第二个也是最近的是动态库(Windows中的动态链接库或DLL和共享库中的DLL或Linux中的SO)。 两者之间的区别在于库中的函数绑定到使用库文件的可执行文件。

链接器是一个实用程序,它使用各种目标文件和库文件来创建可执行文件。 当外部或全局函数或变量用于C源文件时,使用一种标记来告诉链接器需要在该点插入函数或变量的地址。

C编译器只知道它编译的源代码中的内容,并且不知道其他文件(例如目标文件或库)中的内容。 因此,链接器的工作是获取各种目标文件和库,并通过用实际连接替换标记来在部件之间建立最终连接。 因此,链接器是一个实用程序,它将各个组件“链接”在一起,用目标文件和库中的全局函数或变量替换标记,并指向为该全局函数或变量生成的实际目标代码的链接。

在链接器阶段期间,静态库与动态或共享库之间的差异变得明显。 使用静态库时,库的实际目标代码包含在应用程序可执行文件中。 使用动态或共享库时,应用程序可执行文件中包含的目标代码是用于查找共享库的代码,并在运行应用程序时与其连接。

在某些情况下,可以在几个不同的目标文件或库中使用相同的全局函数名称,因此链接器通常只使用它遇到的第一个并发出有关其他文件的警告。

编译和链接摘要

因此,编译和链接C程序的基本过程是:

  • 预处理器实用程序生成要编译的C源

  • 编译器将C源编译为生成一组目标文件的目标代码

  • 链接器将各种目标文件以及任何库链接到可执行文件中

以上是基本过程,但是当使用动态库时,它会变得更复杂,特别是如果生成的应用程序的一部分具有它正在生成的动态库。

装载机

还有应用程序实际加载到内存并开始执行的阶段。 操作系统提供一个实用程序,即加载器,它读取应用程序可执行文件并将其加载到内存中,然后启动应用程序运行。 可执行文件的起始点或入口点在可执行文件中指定,因此在加载程序将可执行文件读入内存后,它将通过跳转到入口点内存地址来启动可执行文件。

链接器可能遇到的一个问题是,有时它在处理需要实际内存地址的目标代码文件时可能遇到标记。 但是,链接器不知道实际的内存地址,因为地址将根据加载应用程序的内存位置而有所不同。 因此,当加载程序将可执行文件加载到内存并准备启动它运行时,链接器会将此标记为装入器实用程序修复的内容。

对于具有硬件支持的虚拟地址到物理地址映射或转换的现代CPU,这个实际存储器地址问题很少成为问题。 每个应用程序都加载到同一个虚拟地址,硬件地址转换处理实际的物理地址。 然而,较旧的CPU或较低成本的CPU(例如缺少用于地址转换的存储器管理单元(MMU)硬件支持的微控制器)仍然需要解决该问题。

入口点和C运行时

最后一个主题是C Runtime以及main()和可执行入口点。

C Runtime是由编译器制造商提供的目标代码,它包含用C编写的应用程序的入口点.main main()函数是编写应用程序的程序员提供的入口点但是这不是入口点。装载机见。 在应用程序启动后,C运行时调用main()函数,C运行时代码为应用程序设置环境。

C运行时不是标准C库。 C运行时的目的是管理应用程序的运行时环境。 标准C库的目的是提供一组有用的实用程序函数,以便程序员不必创建自己的函数。

当加载程序加载应用程序并跳转到C运行时提供的入口点时,C运行时将执行为应用程序提供正确的运行时环境所需的各种初始化操作。 完成此操作后,C Runtime将调用main()函数,以便应用程序开发人员或程序员创建的代码开始运行。 当main()返回或调用exit()函数时,C运行时执行清理和关闭应用程序所需的任何操作。

这是一个非常常见的混乱来源。 我认为理解正在发生的事情最简单的方法就是举一个简单的例子。 暂时忘掉图书馆并考虑以下内容:

 $ cat main.c extern int foo( void ); int main( void ) { return foo(); } $ cat foo.c int foo( void ) { return 0; } $ cc -c main.c $ cc -c foo.c $ cc main.o foo.o 

声明extern int foo( void )执行的function与库的头文件完全相同。 foo.o正在执行库的function。 如果你理解这个例子,为什么cc main.ccc main.o不起作用,那么你就能理解头文件和库之间的区别。

是的,几乎正确。 除了链接器不链接目标文件,还链接库 – 在这种情况下,它是C标准库(libc)链接到您的目标文件。 关于编译阶段+标题和库之间的差异,其余的假设似乎都是正确的。