《程序员的自我修养》学习笔记——揭秘源文件到可执行文件的编译过程【第一弹】

程序代码到可执行程序编译链接过程

预编译

以c++/c 语言为例,预编译阶段的工作有以下几点:

  1. 处理所有#define 及条件预编译指令(如 #if,#ifdef.....),并展开所有宏定义。
  2. 删除所有注释("//" ,"/**/")。
  3. 处理 "#include",将被包含文件插入该预编译指令位置。(整过过程递归进行,因为被包含文件也可能包含其他文件)
  4. 添加行号与文件标识。(用于调试时产生的编译错误及报错等信息)
预编译过程相当于如下命令:

gcc -E hello.c -o hello.i  (-E 表示只进行预编译)
或者
cpp hello.c > hello.i
编译

编译过程可以分为如下步骤:

image

  1. 扫描

  2. 词法分析

    ​ 运用一种类似于有限状态机的算法,将源代码的字符序列分割为一系列记号(关键字、标识符、字面常量、特殊符号等)。【一个名叫lex的程序可以完成这项任务】

  3. 语法分析

    ​ 对由扫描器产生的记号进行语法分析,进而产生语法树。(采用上下文无关的语法分析手段)【同样一个叫做yacc的工具也可完成这项任务】

  4. 语义分析

    ​ 包括静态语义(如声明和类型的匹配、类型的转化等)和动态语义(运行阶段才能确定)。

  5. 源代码优化【这阶段也包括中间代码(例如llvm 中的 IR)的生成】

    ​ 由于直接在语法树上作优化难度较大,源代码优化器通常将语法树转化为中间代码,再进行优化。

  6. 目标代码生成和目标代码优化

    ​ 代码生成器将中间代码转化成目标机器代码。

    ​ 接着目标代码优化器对上述目标代码进行优化。(如选择合适的寻址方式,删除多余指令等)

编译过程相当于如下命令:
gcc -S hello.i -o hello.s (.s 是汇编输出文件的后缀)
或者
gcc -S hello.c -o hello.s  (预编译和编译合并了)

汇编

汇编器将汇编代码转变为机器可以执行的指令。(生成可重定位文件 .o)

编译过程相当于如下命令:
as  hello.s -o hello.o 
或者
gcc -c hello.s -o hello.o 
或者
gcc -c hello.c -hello.o (上面三个过程一步完成)

链接

对于一个复杂的软件,将每个源代码模块独立地翻译,然后组装。这个组装模块的过程就是链接。(主要包括地址和空间分配、符号决议、重定位等步骤)

最基本的静态链接过程:每个模块的源代码文件(如.c)文件经过编译器编译成可重定位文件(Object File,扩展名为.o或.obj),可重定位文件和库一起链接形成最终可执行文件(.out)。

image

链接过程相当于如下命令:

gcc  hello.o -o hello.out 
以如下代码为例:

#include<stdio.h>

int main()
{
printf("hello world");
return 0;
}
预编译(hello.i) 编译(hello.s)
image image
汇编(hello.o) 链接(hello.out)
image image

可重定位文件 [.o 或 .obj]

可重定位文件的格式

目前PC平台流行的可执行文件格式(Executable)主要是:

PE(Windows)和 ELF(Linux)。【两者都发源自 COFF 可执行文件格式】

另外的如ios 是 Mach-O格式android 是dex格式。

而可重定位文件是源代码编译后但未进行链接的中间文件。(Windows 下的.obj 和 Linux 下的.o)。

因此,可重定位文件和可执行文件的内容和结构是很相似的。(可以广义的将二者看作一种类型的文件)

同时动态链接库(Windows 下的.dll 和 Linux 下的.so)和 静态链接库(Windows 下的.lib 和 Linux 下的.a)文件都可按照可执行文件格式存储。

【小技巧: Linux 下可使用file命令查看相应的文件格式】

程序的指令和数据分开存放的好处:

  1. 程序装载后,数据和指令分别映射到两个虚存区域。数据区域对进程而言是可读写的,指令区域对于进程而言是只读的。这样可以防止程序指令被有意或者无意地更改。
  2. 利于提高程序的局部性。(提高缓存的命中率)
  3. 当系统中运行着多个该程序副本时,内存中只需要保存一份该程序的指令部分。(最重要的原因)

原文链接:https://www.cnblogs.com/Only-xiaoxiao/p/17174043.html

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:《程序员的自我修养》学习笔记——揭秘源文件到可执行文件的编译过程【第一弹】 - Python技术站

(0)
上一篇 2023年4月18日
下一篇 2023年4月18日

相关文章

  • C程序 查找数组中常见元素

    下面是查找数组中常见元素的使用攻略: 1. 程序介绍 本程序的功能是,在一个给定的整型数组中,查找出出现次数最多的若干个元素。 2. 环境要求 本程序使用 C 语言编写,需要在计算机上安装 C 编译器才能运行。常用的 C 编译器有 GCC、Clang、Visual Studio 等。此外,程序需要在控制台(命令行)下运行。 3. 程序结构 程序的主要流程分为…

    C 2023年5月9日
    00
  • Linux C线程池简单实现实例

    下面是Linux C线程池简单实现实例的完整攻略。 1. 简介 线程池是一种常见的并发处理技术,其可以在创建一定数量的线程后,接受任务并将任务交给空闲的线程进行处理。从而减少线程创建和销毁的开销,优化了线程资源的利用。在Linux C中实现线程池,可以使用pthread库进行调用。 2. 实现过程 下面是实现Linux C线程池的步骤: 2.1 定义线程池结…

    C 2023年5月22日
    00
  • 利用C语言实现经典多级时间轮定时器

    下面我将详细讲解如何利用C语言实现经典多级时间轮定时器。为了更好地演示,我将分以下五个步骤介绍: 定义时间轮结构体 插入定时器 删除定时器 时间轮转动及定时任务的处理 示例说明 1. 定义时间轮结构体 首先,我们需要定义一个时间轮结构体,用于存储定时器信息和管理定时器。结构体包含时间轮的精度、时间间隔、槽数量等信息,以及一个指针数组用于存储定时器节点。定义如…

    C 2023年5月23日
    00
  • 数据库设计规范化的五个要求 推荐收藏

    数据库设计规范化是一项非常重要的工作,它能够确保数据库的稳定性和可靠性。下面介绍数据库设计规范化的五个要求及相应的推荐收藏。 一、满足第一范式(1NF) 第一范式中要求每个表中的每一列都是原子性的,即不可再分解。如果一个表中存在重复的数据,就需要将其拆分为多个表,每个表中都只包含单一属性。例如,考虑一个音乐播放平台,一个包含歌曲名、歌手和作曲家的表格: So…

    C 2023年5月22日
    00
  • 逍遥自在学C语言 | 位运算符~的高级用法

    前言 在上一篇文章中,我们介绍了^运算符的高级用法,本篇文章,我们将介绍~ 运算符的一些高级用法。 一、人物简介 第一位闪亮登场,有请今后会一直教我们C语言的老师 —— 自在。 第二位上场的是和我们一起学习的小白程序猿 —— 逍遥。 二、相反数 我们可以利用负数的补码性质,来获得一个正数的相反数 #include <stdio.h> int ma…

    C语言 2023年4月17日
    00
  • MathWorks MATLAB R2022a中文版激活密钥+详细安装教程(含下载)

    下面我为你详细讲解“MathWorks MATLAB R2022a中文版激活密钥+详细安装教程(含下载) ”的完整攻略。 下载MATLAB R2022a 首先,你需要进入官网下载MATLAB R2022a的安装文件。在下载页面选择“试用版”,然后选择相应的操作系统,下载完成后解压。 安装MATLAB R2022a 点击解压出来的“setup.exe”文件,选…

    C 2023年5月22日
    00
  • C语言 如何求两整数的最大公约数与最小公倍数

    下面是C语言如何求两整数的最大公约数与最小公倍数的完整攻略。 求最大公约数 理论知识 两个数的最大公约数是它们的公共因数中最大的一个数。求两个数的最大公约数也就是求这两个数的所有公因数中最大的一个数。 有很多算法可以用来求最大公约数,其中最常用的两种是辗转相减法和欧几里得算法(辗转相除法)。 代码示例 #include <stdio.h> int…

    C 2023年5月23日
    00
  • C++使用递归和非递归算法实现的二叉树叶子节点个数计算方法

    C++使用递归和非递归算法实现的二叉树叶子节点个数计算方法 计算一个二叉树中叶子节点的个数是二叉树的常见问题之一。使用递归或非递归算法都可以实现这个功能,下面我们逐步讲解两种算法的实现过程。 递归算法 递归算法是一种自上而下、分而治之的算法思想。在二叉树中,递归算法的实现也是先计算根节点,再计算左子树和右子树,最终得出结果。 递归计算二叉树叶子节点个数的方法…

    C 2023年5月23日
    00
合作推广
合作推广
分享本页
返回顶部