C 库是与目标代码链接 还是先与源代码链接,然后才与目标代码链接?我的意思是,看看卡迪夫计算机科学与信息学院网站上的图片 :
“奇怪”的是,在生成目标代码之后,库正在被链接。我的意思是,我们在放置包含时使用源代码!
所以..这实际上是如何工作的?谢谢!
C 库是与目标代码链接 还是先与源代码链接,然后才与目标代码链接?我的意思是,看看卡迪夫计算机科学与信息学院网站上的图片 :
“奇怪”的是,在生成目标代码之后,库正在被链接。我的意思是,我们在放置包含时使用源代码!
所以..这实际上是如何工作的?谢谢!
那个图是对的。
当您#include
使用标头时,它实际上将该标头复制到您的文件中。标头是类型和函数声明和常量等的列表,但不包含任何实际代码(尽管有 C++ 和内联函数)。
举个例子:library.h
int foo(int num);
图书馆.c
int foo(int num)
{
return num * 2;
}
你的代码.c
#include <stdio.h>
#include "library.h"
int main(void)
{
printf("%d\n", foo(100));
return 0;
}
当你#include
library.h时,你会得到foo()
. 在这一点上,编译器对 foo() 或它的作用一无所知。foo()
尽管如此,编译器仍可以自由插入调用。链接器foo()
在youcode.c中看到了对的调用,并在library.c中看到了代码,就知道对的任何调用都foo()
应该转到该代码。
换句话说,编译器告诉链接器调用什么函数,并且链接器(给定所有目标代码)知道该函数实际在哪里。
(感谢 Fiddleing Bits 的更正。)
查看工具链中的每个部分的作用可能很有启发性,因此请使用图像中的框。
预处理器
这实际上是一个文本编辑器,做了一堆替换(好吧,真的是过于简单化了)。预处理器所做的一些事情是:
#define PI 3.1415
在我们的文件中,然后我们有一行,例如angle = angle * PI / 180;
pre=processor 会将这一行转换为angle = angle * 3.1414 / 180;
#include
,我们可以想象预处理器会去获取该文件的全部内容并将文件上的内容粘贴到该文件的#include
位置。(然后我们回去执行替换。#pragma
指令将选项传递给编译器。最后,我们可以通过使用 gcc 的 -E 选项来查看运行预处理器的结果。
编译器
预处理器的输出仍然是文本,它不包含编译器处理文件所需的所有内容。现在编译器做了很多事情(当我描述这个过程时,我通常会打破盒子)。编译器将处理文本,对其进行词法分析,将其传递给验证程序是否满足语言语法的解析器,输出该语言的中间表示,执行优化并生成汇编代码。
我们可以通过使用 gcc 的 -s 选项看到运行到汇编程序的结果。
汇编器
编译器的输出是汇编列表,然后将其传递给汇编器(Linux 上最常见的“gas”(GNU 汇编器)),将汇编代码转换为机器代码。此外,汇编器的任务是构建未定义引用的列表(即,您编写的函数的库函数在另一个源文件中实现。)
我们可以通过使用 gcc 的 -c 选项来查看得到汇编器输出的结果。
链接器
链接器的输入将是汇编器的输出(通常称为目标文件并使用扩展名“o”)以及各种库。从概念上讲,链接器负责将所有内容连接在一起,包括修复对库中函数的调用。通常,在Linux中执行链接的程序是ld,我们只需运行gcc就可以看到链接的结果,不需要任何特殊的命令行选项。
我已经简化了链接器的讨论,我希望我能让你了解链接器的作用。
我对您引用的图像的唯一问题是我会将阶段“对象代码”移动到汇编器框的正下方,同时我会将标记为“库”的箭头向下移动。我觉得这表明来自汇编器的目标代码与库相结合,并且这些由链接器组合成可执行文件。
库中的包含通常只包含库接口 - 所以在最简单的情况下,库提供的 .h 文件包含函数声明,编译后的函数在库文件中。因此,您使用库头文件中提供的库函数声明来编译源代码,然后链接器将编译器库函数添加到您的可执行文件中。