编译和链接过程如何工作?

(注意:这本来是Stack Overflow的C ++ FAQ的条目。如果您想批评以这种形式提供FAQ的想法,那么在所有这些都开始的meta上的张贴将是这样做的地方。该问题在C ++聊天室中进行监控,该问题最初是从FAQ想法开始的,所以提出这个想法的人很可能会读懂您的答案。)


#1楼

在CProgramming.com上讨论了此主题:
https://www.cprogramming.com/compilingandlinking.html

这是那里的作者写的:

编译与创建可执行文件并不完全相同! 相反,创建可执行文件是一个分为两个部分的多阶段过程:编译和链接。 实际上,即使程序“可以正常编译”,由于链接阶段中的错误,它实际上也可能无法正常工作。 从源代码文件到可执行文件的整个过程可能更好地称为构建。

汇编

编译是指处理源代码文件(.c,.cc或.cpp)和创建“目标”文件。 此步骤不会创建用户可以实际运行的任何内容。 相反,编译器仅产生与已编译的源代码文件相对应的机器语言指令。 例如,如果您编译(但不链接)三个单独的文件,则将创建三个作为输出的目标文件,每个文件的名称均为.o或.obj(扩展名取决于编译器)。 这些文件中的每一个都包含将源代码文件转换为机器语言文件的翻译-但您还不能运行它们! 您需要将它们转换为操作系统可以使用的可执行文件。 这就是链接器进入的地方。

连结中

链接是指从多个目标文件创建单个可执行文件。 在此步骤中,链接器通常会抱怨未定义的函数(通常是main本身)。 在编译期间,如果编译器找不到特定功能的定义,则仅假设该功能在另一个文件中定义。 如果不是这种情况,那么编译器将无法知道-它一次不会查看多个文件的内容。 另一方面,链接器可能会查看多个文件,并尝试查找未提及功能的引用。

您可能会问,为什么有单独的编译和链接步骤。 首先,以这种方式实现事情可能更容易。 编译器完成其任务,链接器完成其任务-通过将功能分开,可以降低程序的复杂性。 另一个(更明显的)优点是,这允许创建大型程序,而不必在每次更改文件时都重做编译步骤。 而是使用所谓的“条件编译”,仅编译那些已更改的源文件; 对于其余部分,目标文件对于链接器来说是足够的输入。 最后,这使实现预编译代码库变得简单:只需创建目标文件并像其他任何目标文件一样链接它们即可。 (顺便说一句,每个文件都与其他文件中包含的信息分开编译的事实被称为“单独的编译模型”。)

为了获得条件编译的全部好处,获得一个程序来帮助您可能比尝试记住自上次编译以来更改过的文件要容易得多。 (当然,您可以重新编译时间戳大于相应目标文件时间戳的每个文件。)如果您正在使用集成开发环境(IDE),它可能已经为您解决了。 如果您使用的是命令行工具,那么大多数* nix发行版中都会附带一个名为make的漂亮实用程序。 除了条件编译外,它还有其他一些不错的编程功能,例如允许对程序进行不同的编译-例如,如果您的版本产生用于调试的详细输出。

了解编译阶段和链接阶段之间的区别可以使寻找错误的过程变得更加容易。 编译器错误通常本质上是语法上的-缺少分号,多余的括号。 链接错误通常与缺少或多个定义有关。 如果从链接器中收到多次定义函数或变量的错误,则表明该错误是两个源代码文件具有相同的函数或变量。


#2楼

瘦弱的是,CPU从内存地址加载数据,将数据存储到内存地址,并从内存地址中依次执行指令,并在处理的指令序列中有一些条件性的跳转。 这三种指令中的每一种都涉及计算要在机器指令中使用的存储单元的地址。 因为机器指令的长度取决于所涉及的特定指令,并且由于我们在构建机器代码时将它们的可变长度串在一起,所以计算和建立任何地址都涉及两步过程。

首先,在我们知道每个单元中到底发生了什么之前,我们会尽最大可能布置内存分配。 我们找出字节或字,或构成指令,文字和任何数据的任何内容。 我们只是开始分配内存并建立将在运行时创建程序的值,并记下需要返回并固定地址的任何地方。 在那个地方,我们放置了一个虚拟对象来填充该位置,以便我们可以继续计算内存大小。 例如,我们的第一个机器代码可能占用一个单元。 下一个机器代码可能需要3个单元,其中包括一个机器代码单元和两个地址单元。 现在我们的地址指针是4。我们知道机器单元中的内容,即操作码,但是我们必须等待计算地址单元中的内容,直到我们知道数据将位于何处,即数据将是什么。该数据的机器地址。

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

链接器是允许单独编译代码块的工具。 这可以加快构建代码的总体过程,并为以后使用这些块提供一些灵活性,换句话说,可以将它们重新放置在内存中,例如,将1000个添加到每个地址,以将块划分为1000个地址单元。

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

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

显然,我对此进行了简化,因此您可以尝试掌握它,而我故意不使用目标文件,符号表等术语,这对我来说是造成混淆的一部分。


#3楼

查看网址: http : //faculty.cs.niu.edu/~mcmahon/CS241/Notes/compile.html
此URL中清楚地介绍了C ++的完整编译过程。


#4楼

GCC通过4个步骤将C / C ++程序编译为可执行文件。

例如,“ gcc -o hello.exe hello.c ”执行如下:

1.预处理

通过GNU C预处理程序(cpp.exe)进行预处理,其中包括标头(#include)并扩展宏(#define)。

cpp hello.c> hello.i

生成的中间文件“ hello.i”包含扩展的源代码。

2.编译

编译器将预处理的源代码编译为特定处理器的汇编代码。

gcc -S hello.i

-S选项指定产生汇编代码,而不是目标代码。 生成的程序集文件为“ hello.s”。

3.组装

汇编程序(as.exe)将汇编代码转换为目标文件“ hello.o”中的机器代码。

作为-o hello.o hello.s

4.连结器

最后,链接器(ld.exe)将目标代码与库代码链接在一起,以生成可执行文件“ hello.exe”。

ld -o hello.exe hello.o ...库...


#5楼

C ++程序的编译涉及三个步骤:

  1. 预处理:预处理需要花费C ++源代码文件,并处理所述#include S, #define S和其它预处理指令。 此步骤的输出是一个没有预处理程序指令的“纯” C ++文件。

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

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

预处理

预处理程序处理预处理程序指令 ,例如#include#define 。 它与C ++的语法无关,这就是为什么必须谨慎使用它的原因。

通过将#include指令替换为相应文件的内容(通常只是声明),替换宏( #define )并根据#if选择文本的不同部分,一次可以处理一个C ++源文件。 #ifdef#ifndef指令。

预处理器处理预处理令牌流。 宏替换定义为用其他令牌替换令牌(运算符##在有意义时启用两个令牌的合并)。

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

巧妙地使用#if#error指令,可能会在此阶段产生一些错误。

汇编

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

目标文件可以引用未定义的符号。 使用声明而不提供定义时就是这种情况。 编译器并不介意这一点,只要源代码格式正确,它就会愉快地生成目标文件。

编译器通常可以让您在此时停止编译。 这非常有用,因为使用它可以分别编译每个源代码文件。 这样做的好处是,仅更改单个文件就不需要重新编译所有内容

可以将生成的目标文件放在称为静态库的特殊归档中,以方便以后重用。

在此阶段,将报告“常规”编译器错误,例如语法错误或失败的重载解析错误。

连结中

链接器根据编译器生成的目标文件生成最终编译输出。 此输出可以是共享(或动态)库(虽然名称相似,但与前面提到的静态库没有太多共同点)或可执行文件。

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

在此阶段,最常见的错误是缺少定义或重复定义。 前者意味着要么定义不存在(即未编写),要么没有将定义所驻留的目标文件或库提供给链接器。 后者是显而易见的:在两个不同的目标文件或库中定义了相同的符号。


#6楼

在标准方面:

  • 转换单元是源文件,包含的头文件和源文件的组合减去有条件包含预处理器指令跳过的任何源代码行。

  • 该标准定义了翻译的9个阶段。 前四个对应于预处理,后三个对应于编译,下一个对应于模板的实例化(生成实例化单元 ),最后一个对应于链接。

实际上,第八阶段(模板的实例化)通常在编译过程中完成,但是一些编译器将其延迟到链接阶段,而有些则将其扩展到两个阶段。

编译/链接过程如何工作?相关推荐

  1. C语言的编译链接过程详解

    学过C语言的人都应该知道,我们所编辑的C语言程序是不能直接放到机器上运行的,它只不过是一个带".c"后缀的文件(也称为源代码)而已,需要经过一定的处理才能转换成机器上可运行的可执行 ...

  2. C语言的编译链接过程的介绍

    发布时间: 2012-11-08 10:17    作者: 未知    来源: 51Testing软件测试网采编 字体:  小  中  大  | 上一篇 下一篇 | 打印  | 我要投稿  | 推荐标 ...

  3. EWARM IAR5.4编译链接过程, 程序运行阶段, ICF ilink配置文件

    编译链接过程 编译器, 汇编器和链接器完成将代码转换成可执行文件的过程. 编译过程 首先是编译器和汇编器, 这是链接之前的工作, 生成可重定向的目标文件, 进而可以生成库文件: 命令为: iccarm ...

  4. c语言程序链接过程,C语言简明教程(二):C程序编译链接过程和实例对照详解...

    不像高级编程语言,在C语言开发中,了解其编译链接过程显得相对重要,因为C语言是较为底层的语言,很多时候我们调试C程序或者解决其它问题都可能会涉及到C编译链接的相关知识,例如编译动态库或者静态库.下面我 ...

  5. cc++编译链接过程

    这篇文章写的真不错! 有些人写C/C++(以下假定为C++)程序,对unresolved external link或者duplicated external simbol的错误信息不知所措(因为这样 ...

  6. windows环境下gcc的使用(二):gcc命令与程序编译链接过程

    测试Linux命令 上一篇博客已经安装好了cygwin,相当于在windows平台上已经搭建好了一个模拟Linux的环境,那么在cygwin的终端(Cygwin64 Terminal)中测试Linux ...

  7. linux 编译链接过程,编译程序 一:linux程序编译过程(一)-编译和链接

    大家肯定都知道计算机程序设计语言通常分为机器语言.汇编语言和高级语言三类.高级语言需要通过翻译成机器语言才能执行,而翻译的方式分为两种,一种是编译型,另一种是解释型,因此我们基本上将高级语言分为两大类 ...

  8. 编译链接过程中出现“无法解析的外部符号”,原因及解决办法总结

    1.错误为:error LNK2019: 无法解析的外部符号 ___report_rangecheckfailure,该符号在函数 _OBJ_create_objects 中被引用 原因:__repo ...

  9. 程序的编译和链接过程

    一.虚拟机.linux简介 简单介绍一下虚拟机还有就是各种操作系统,比如centos,Ubuntu 操作系统:linux(centos.Ubuntu.redhat),Android,Windows(x ...

最新文章

  1. ElasticSearch安装使用 操作索引
  2. 漫画:什么是LRU算法?
  3. PHP学习笔记二: 面向对象设计
  4. CSS3边框图片-像素虚边的问题
  5. 1.7 ConcurrentHashMap增删改查
  6. 三角形最佳路径问题(信息学奥赛一本通-T1288)
  7. 面向对象实现放大镜_面向音乐家和音乐爱好者的开放式硬件:耳机,放大器等
  8. mysql实现了四种通信协议_Mysql通信协议-阿里云开发者社区
  9. 2021-06-23元素的排列,浮动和display
  10. 张朝阳5G寻路与搜狐奇兵
  11. cpu占用突然到百分百又降下去_cpu使用率忽高忽低
  12. 《黑天鹅》纳西姆-尼古拉斯-塔勒布_epub+mobi+azw3
  13. 使用阿里的【字体图标】期望的效果
  14. 数学基础知识(公式)不用学太深,了解基本公式即可,遇到问题再查吧。
  15. 使用PE工具制作U盘启动盘
  16. 【学习笔记】SQL数据库
  17. 情感分析的分类,情感分析模型有哪些,情感分析的应用场景,情感分析的发展趋势
  18. Android.Cynos.7.origin木马已感染900万+安卓设备
  19. MacBook替换登录界面壁纸
  20. c语言编写计算ackerman函数的递归函数ack(n,x,y),第六章函数与宏定义实验报告二...

热门文章

  1. WMS(一):Window的添加过程
  2. 安卓开发重磅炸弹!程序员福利!《高级Kotlin强化实战学习手册(附Demo)》开放下载!
  3. Java CAS AtomicInteger使用
  4. Android CountDownTimer倒计时器的使用
  5. 了解ViewConfiguration
  6. 已知3个坐标点xy画圆弧_这25张图片,让你彻底看懂25个复杂的数学公式!
  7. (0014)iOS 开发之Mac自带的Apache本地服务器玩耍(01)
  8. vue父子组件传值,sync语法糖
  9. 使用 JProbe 调试 Linux 内核(转)
  10. VS2010/MFC编程入门之十七(对话框:文件对话框)