为了账号安全,请及时绑定邮箱和手机立即绑定

编译/链接过程是如何工作的?

编译/链接过程是如何工作的?

C C++
繁花如伊 2019-05-30 17:20:24
编译/链接过程是如何工作的?编译和链接过程是如何工作的?(注:这是指堆栈溢出的C+常见问题。如果您想批评以这种形式提供常见问题的想法,那么在元网站上发布的文章引发了这一切就是这样做的地方。对该问题的回答将在C+聊天室你的答案很可能会被那些想出这个想法的人读懂。)
查看完整描述

4 回答

?
烙印99

TA贡献1829条经验 获得超13个赞

C+程序的编译包括三个步骤:

  1. 预处理:预处理程序采用C+源代码文件,并处理#includeS,#defineS和其他预处理指令。这个步骤的输出是一个“纯”C+文件,没有预处理指令。

  2. 编译:编译器获取预处理器的输出并从中生成一个对象文件.

  3. 链接:链接器获取编译器生成的对象文件,并生成库或可执行文件。

预处理

预处理程序处理预处理指令,就像#include#define。它不知道C+的语法,这就是为什么必须谨慎使用它的原因。

它一次工作在一个C+源文件上,方法是#include具有相应文件内容的指令(通常只是声明),替换宏(#define),并根据以下内容选择文本的不同部分:#if#ifdef#ifndef指令。

预处理器工作在预处理令牌流上。宏替换定义为用其他令牌替换令牌(操作符)。##当有意义时,启用合并两个令牌)。

在所有这些之后,预处理器产生一个单一的输出,这是由上面描述的转换产生的令牌流。它还添加了一些特殊的标记,告诉编译器每一行来自哪里,这样它就可以使用这些标记来生成合理的错误消息。

在此阶段,可以通过巧妙地使用#if#error指令。

编撰

编译步骤对预处理程序的每个输出执行。编译器解析纯C+源代码(现在没有任何预处理器指令)并将其转换为程序集代码。然后调用底层后端(工具链中的汇编程序),将该代码组装成生成某种格式的实际二进制文件的机器代码(ELF,COFF,a.out,.)。此对象文件包含输入中定义的符号的编译代码(二进制形式)。对象文件中的符号按名称引用。

对象文件可以引用未定义的符号。如果使用声明,而不提供声明的定义,则会出现这种情况。编译器不介意这一点,只要源代码格式良好,编译器就会很高兴地生成对象文件。

编译器通常允许您在此时停止编译。这非常有用,因为使用它,您可以分别编译每个源代码文件。它提供的优点是您不需要重新编译一切如果您只更改一个文件。

生成的对象文件可以放在称为静态库的特殊档案中,以便以后更容易重用。

在这个阶段,报告了“常规”编译器错误,比如语法错误或失败的过载解析错误。

链接

链接器是编译器生成的对象文件的最终编译输出。这个输出可以是一个共享的(或者是动态的)库(虽然名称相似,但它们与前面提到的静态库没有多少共同点),也可以是一个可执行文件。

它通过用正确的地址替换对未定义符号的引用来链接所有对象文件。这些符号中的每一个都可以在其他对象文件或库中定义。如果它们是在标准库以外的库中定义的,则需要将它们告知链接器。

在这个阶段,最常见的错误是缺少定义或重复定义。前者意味着定义不存在(即它们不被写入),或者它们所在的对象文件或库没有提供给链接器。后者是显而易见的:在两个不同的对象文件或库中定义了相同的符号。


查看完整回答
反对 回复 2019-05-30
?
慕雪6442864

TA贡献1812条经验 获得超5个赞

精简是指CPU从内存地址加载数据,将数据存储到内存地址,然后从内存地址顺序执行指令,在处理的指令序列中有一些条件跳转。这三种指令中的每一种都涉及到计算要在机器指令中使用的存储器单元的地址。由于机器指令的长度取决于所涉及的特定指令,而且由于我们在构建机器代码时将其中的可变长度串在一起,所以在计算和构建任何地址时需要两个步骤。

首先,我们尽可能地安排内存分配,然后才能知道每个单元的确切情况。我们计算出字节,或单词,或构成指令、文字和任何数据的任何东西。我们只是开始分配内存和构建值,这些值将在我们运行时创建程序,并记下我们需要返回并修复地址的任何地方。在那个地方,我们放置一个假人来填充位置,这样我们就可以继续计算内存大小。例如,我们的第一个机器代码可能需要一个单元格。下一个机器代码可能包含3个单元,包括一个机器代码单元和两个地址单元。现在我们的地址指针是4。我们知道机器单元是什么,这是OP代码,但是我们必须等待计算地址单元格中的内容,直到我们知道数据的位置,即数据的机器地址是什么。

如果只有一个源文件,理论上编译器可以在没有链接器的情况下生成完全可执行的机器代码。在两次传递过程中,它可以计算出任何机器加载或存储指令所引用的所有数据单元的所有实际地址。它可以计算任何绝对跳转指令所引用的所有绝对地址。这是一个简单的编译器,就像Forth Work中的编译器,没有链接器。

链接器允许单独编译代码块。这可以加快构建代码的整个过程,并允许在以后如何使用这些块时具有一定的灵活性,换句话说,它们可以在内存中重新定位,例如在每个地址中添加1000个地址,以使块增加1000个地址单元格。

所以编译器输出的是尚未完全构建的粗略的机器代码,而是布局好的,这样我们就知道了所有东西的大小,换句话说,我们就可以开始计算所有绝对地址的位置。编译器还输出一个符号列表,这些符号是名称/地址对。符号将模块中机器代码中的内存偏移量与名称相关联。偏移量是到模块中符号的存储器位置的绝对距离。

那就是我们找到链接器的地方。链接器首先将所有这些机器代码块连接到一起,并记下每个代码的起始位置。然后,通过将模块内部的相对偏移量和模块在较大布局中的绝对位置相加,计算要固定的地址。

显然,我过于简化了这一点,所以您可以尝试理解它,而且我故意不使用对象文件、符号表等术语。这对我来说是混乱的一部分。


查看完整回答
反对 回复 2019-05-30
  • 4 回答
  • 0 关注
  • 764 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信