一、概述

C语言的编译链接过程要把我们编写的一个c程序(源代码)转换成可以在硬件上运行的程序(可执行代码),需要进行编译和链接。编译就是把文本形式源代码翻译为机器语言形式的目标文件的过程。链接是把目标文件、操作系统的启动代码和用到的库文件进行组织形成最终生成可加载、可执行代码的过程。

过程图解如下:

  1. 预处理器:将.c 文件转化成 .i文件,使用的gcc命令是:gcc –E,对应于预处理命令cpp;
  2. 编译器:将.c/.h文件转换成.s文件,使用的gcc命令是:gcc –S,对应于编译命令 cc –S;
  3. 汇编器:将.s 文件转化成 .o文件,使用的gcc 命令是:gcc –c,对应于汇编命令是 as;
  4. 链接器:将.o文件转化成可执行程序,使用的gcc 命令是: gcc,对应于链接命令是 ld;
  5. 加载器:将可执行程序加载到内存并进行执行,loader和ld-linux.so。

二、编译过程

编译过程又可以分成两个阶段:编译和汇编。

2.1编译

编译是指编译器读取源程序(字符流),对之进行词法和语法的分析,将高级语言指令转换为功能等效的汇编代码。

源文件的编译过程包含两个主要阶段:

第一个阶段是预处理阶段,在正式的编译阶段之前进行。预处理阶段将根据已放置在文件中的预处理指令来修改源文件的内容。

主要是以下几方面的处理:

  1. 宏定义指令,如 #define a b 对于这种伪指令,预编译所要做的是将程序中的所有a用b替换,但作为字符串常量的 a则不被替换。还有 #undef,则将取消对某个宏的定义,使以后该串的出现不再被替换。
  2. 条件编译指令,如#ifdef,#ifndef,#else,#elif,#endif等。 这些伪指令的引入使得程序员可以通过定义不同的宏来决定编译程序对哪些代码进行处理。预编译程序将根据有关的文件,将那些不必要的代码过滤掉
  3. 头文件包含指令,如#include "FileName"或者#include 等。 该指令将头文件中的定义统统都加入到它所产生的输出文件中,以供编译程序对之进行处理。
  4. 特殊符号,预编译程序可以识别一些特殊的符号。 例如在源程序中出现的LINE标识将被解释为当前行号(十进制数),FILE则被解释为当前被编译的C源程序的名称。预编译程序对于在源程序中出现的这些串将用合适的值进行替换。

头文件的目的主要是为了使某些定义可以供多个不同的C源程序使用,这涉及到头文件的定位即搜索路径问题。头文件搜索规则如下:

  1. 所有header file的搜寻会从-I开始
  2. 然后找环境变量 C_INCLUDE_PATH,CPLUS_INCLUDE_PATH,OBJC_INCLUDE_PATH指定的路径
  3. 再找默认目录(/usr/include、/usr/local/include、/usr/lib/gcc-lib/i386-linux/2.95.2/include......)

第二个阶段编译、优化阶段,编译程序所要作得工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码。

2.2汇编

汇编实际上指汇编器(as)把汇编语言代码翻译成目标机器指令的过程。目标文件中所存放的也就是与源程序等效的目标的机器语言代码。目标文件由段组成。通常一个目标文件中至少有两个段:

  • 代码段:该段中所包含的主要是程序的指令。该段一般是可读和可执行的,但一般却不可写。
  • 数据段:主要存放程序中要用到的各种全局变量或静态的数据。一般数据段都是可读,可写,可执行的。

2.3目标文件(Executable and Linkable Format)

  1. 可重定位(Relocatable)文件:由编译器和汇编器生成,可以与其他可重定位目标文件合并创建一个可执行或共享的目标文件;
  2. 共享(Shared)目标文件:一类特殊的可重定位目标文件,可以在链接(静态共享库)时加入目标文件或加载时或运行时(动态共享库)被动态的加载到内存并执行;
  3. 可执行(Executable)文件:由链接器生成,可以直接通过加载器加载到内存中充当进程执行的文件。

2.4 静态库与动态库

静态库(static library)就是将相关的目标模块打包形成的单独的文件。使用ar命令。

静态库的优点在于:

  • 程序员不需要显式的指定所有需要链接的目标模块,因为指定是一个耗时且容易出错的过程;
  • 链接时,连接程序只从静态库中拷贝被程序引用的目标模块,这样就减小了可执行文件在磁盘和内存中的大小。

动态库(dynamic library)是一种特殊的目标模块,它可以在运行时被加载到任意的内存地址,或者是与任意的程序进行链接。

动态库的优点在于:

  • 更新动态库,无需重新链接;对于大系统,重新链接是一个非常耗时的过程;
  • 运行中可供多个程序使用,内存中只需要有一份,节省内存。

三、链接过程

链接器主要是将有关的目标文件彼此相连接生成可加载、可执行的目标文件。链接器的核心工作就是符号表解析和重定位。

3.1 链接的时机:

  1. 编译时,就是源代码被编译成机器代码时(静态链接器负责);
  2. 加载时,也就是程序被加载到内存时(加载器负责);
  3. 运行时,由应用程序来实施(动态链接器负责)。

3.2 链接的作用(软件复用):

  1. 使得分离编译成为可能;
  2. 动态绑定(binding):使定义、实现、使用分离

3.3 静态库搜索路径(由静态链接器负责)

  1. gcc先从-L寻找;
  2. 再找环境变量LIBRARY_PATH指定的搜索路径;
  3. 再找内定目录 /lib /usr/lib /usr/local/lib 这是当初compile gcc时写在程序内的。

3.4 动态库搜索路径(由动态链接器负责)

  1. 编译目标代码时指定的动态库搜索路径-L;
  2. 环境变量LD_LIBRARY_PATH指定的动态库搜索路径;
  3. 配置文件/etc/ld.so.conf中指定的动态库搜索路径;
  4. 默认的动态库搜索路径/lib /usr/lib/ /usr/local/lib

3.5 静态链接(编译时)

链接器将函数的代码从其所在地(目标文件或静态链接库中)拷贝到最终的可执行程序中。这样该程序在被执行时这些代码将被装入到该进程的虚拟地址空间中。静态链接库实际上是一个目标文件的集合,其中的每个文件含有库中的一个或者一组相关函数的代码。

为创建可执行文件,链接器必须要完成的主要任务:

  1. 符号解析:把目标文件中符号的定义和引用联系起来;
  2. 重定位:把符号定义和内存地址对应起来,然后修改所有对符号的引用。

关于符号表和符号解析以及重定位的分析后续学习。

3.6 动态链接(加载、运行时)

在此种方式下,函数的定义在动态链接库或共享对象的目标文件中。在编译的链接阶段,动态链接库只提供符号表和其他少量信息用于保证所有符号引用都有定义,保证编译顺利通过。动态链接器(ld-linux.so)链接程序在运行过程中根据记录的共享对象的符号定义来动态加载共享库,然后完成重定位。在此可执行文件被执行时,动态链接库的全部内容将被映射到运行时相应进程的虚地址空间。动态链接程序将根据可执行程序中记录的信息找到相应的函数代码。

四、加载过程

加载器把可执行文件从外存加载到内存并进行执行。 Linux中进程运行时的内存映像如下:

加载过程如下:

加载器首先创建如上图所示的内存映像,然后根据段头部表,把目标文件拷贝到内存的数据和代码段中。然后,加载器跳转到程序入口点(即符号_start 的地址),执行启动代码(startup code),启动代码的调用顺序如所示:

五、处理目标的常用工具

UNIX系统提供了一系列工具帮助理解和处理目标文件。GNUbinutils 包也提供了很多帮助。这些工具包括:

  • AR :创建静态库,插入、删除、列出和提取成员;
  • STRINGS :列出目标文件中所有可以打印的字符串;
  • STRIP :从目标文件中删除符号表信息;
  • NM :列出目标文件符号表中定义的符号;
  • SIZE :列出目标文件中节的名字和大小;
  • READELF :显示一个目标文件的完整结构,包括ELF 头中编码的所有信息。
  • OBJDUMP :显示目标文件的所有信息,最有用的功能是反汇编.text节中的二进制指令。
  • LDD :列出可执行文件在运行时需要的共享库。

C编译器、链接器、加载器详解相关推荐

  1. Java类的加载过程详解 面试高频!!!值得收藏!!!

    受多种情况的影响,又开始看JVM 方面的知识. 1.Java 实在过于内卷,没法不往深了学. 2.面试题问的多,被迫学习. 3.纯粹的好奇. 很喜欢一句话: 八小时内谋生活,八小时外谋发展. 望别日与 ...

  2. 中yeti不能加载_第二十章_类的加载过程详解

    类的加载过程详解 概述 在 Java 中数据类型分为基本数据类型和引用数据类型.基本数据类型由虚拟机预先定义,引用数据类型则需要进行类的加载 按照 Java 虚拟机规范,从 Class 文件到加载到内 ...

  3. 类加载顺序及加载过程详解

    转自: 类加载顺序及加载过程详解 下文笔者讲述类的加载顺序及加载过程的详解说明,如下所示 java创建对象的方式分为以下四种 new 反射克隆反序列化 class对象获取的方式分享 //没有完成初始化 ...

  4. 新手必看:访问url到加载全过程详解(看完不会我吃shi)

    新手必看:访问url到加载全过程详解(看完不会我吃shi) 1.放在前面:新手必须知道的那些概念 1.1 什么是IP.域名.主机名.url.服务器 1.2 http & https 1.3 O ...

  5. android 图片横竖判断_Android横竖屏切换及其对应布局加载问题详解

    本文为大家分享了Android横竖屏切换及其对应布局加载问题,供大家参考,具体内容如下 第一,横竖屏切换连带横竖屏布局问题: 如果要让软件在横竖屏之间切换,由于横竖屏的高宽会发生转换,有可能会要求不同 ...

  6. php自动加载类与路由,PHP实现路由与类自动加载步骤详解

    这次给大家带来PHP实现路由与类自动加载步骤详解,PHP实现路由与类自动加载步骤详解的注意事项有哪些,下面就是实战案例,一起来看一下. 项目目录如下 入口文件index.php<?php def ...

  7. Trembling ! Java类的加载过程详解(加载验证准备解析初始化使用卸载)

    [1]类的生命周期 一个类从加载进内存到卸载出内存为止,一共经历7个阶段: 加载->验证->准备->解析->初始化->使用->卸载 其中,类加载包括5个阶段: 加载 ...

  8. jboss之启动加载过程详解(-)

    今天看了看jboss的boot.log和server.log日志,结合自己的理解和其他的资料,现对jboss的启动和加载过程做出如下总结: 本文以JBoss Application Server 4. ...

  9. Java虚拟机(JVM)之类的加载过程详解

    java程序在对某个类进行引用.使用时,就会开始对该类进行加载,比如直接使用类加载器进行显式加载.创建该类的对象.使用该类的类变量等情况.类的加载是通过java虚拟机的类加载子系统完成的.类的加载主要 ...

  10. 类的加载过程详解:加载、验证、准备、解析、初始化

    想要弄明白的知识点: 类加载的过程,加载.验证.准备.解析.初始化.每个部分详细描述. 加载阶段读入.class文件,class文件时二进制吗,为什么需要使用二进制的方式? 验证过程是防止什么问题?验 ...

最新文章

  1. 如何阻止子元素触发父元素的事件
  2. 旧式计算机,西雅图计算机博物馆 “复活”1973年老式计算机
  3. 第九章 关联数组/哈希表
  4. 在字符串末尾添加字符使其成为回文串
  5. curl怎么输出赋值_python怎么实现循环
  6. POP3口令扫描案例
  7. .NET Core中的验证组件FluentValidation的实战分享
  8. STL_stack/queue
  9. 计算机ensp项目无法运行,eNSP常见问题及解决办法
  10. 事务的隔离级别与锁的申请和释放
  11. Servlet(二)GenericServlet
  12. 网站快速收录-网站快速收录工具下载免费
  13. 华为eSight 监控AR系列路由器端口流量
  14. Windows11 安装安卓子系统详细教程
  15. 【技巧】屏蔽百度搜索热点和相关软件推荐等(提高注意力)
  16. 阿里OSS图片持久化,裁切,缩放,格式转换等
  17. TZT3801G无线振弦在线监测系统
  18. 工控液晶屏开机白屏怎么回事,开机白屏解决方法?
  19. Kubernetes--k8s---进阶--管理工具helm--helm全面介绍
  20. 会话/序列推荐:Caser、SASRec、BERT4Rec [Session based / Sequential Recommendation]

热门文章

  1. 【鸿蒙 HarmonyOS】Ability 简介 ( 简介 | 创建应用 | Page Ability 生命周期 )
  2. 【Android 内存优化】内存抖动 ( 垃圾回收算法总结 | 分代收集算法补充 | 内存抖动排查 | 内存抖动操作 | 集合选择 )
  3. pandas模块学习
  4. 08、MySQL—字符串型
  5. 洛谷 P1028 数的计算
  6. 最常用的15大Eclipse开发快捷键技巧
  7. Unable to locate package错误解决办法
  8. Webbrowers控件的小技巧
  9. python 用twisted 问题 zope.interface
  10. C#实现MVC模式简要方法(2)