【Linux】gcc-程序的翻译四个阶段(图示)

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

因为淋过雨所以懂的为别人撑伞因为迷茫过所以懂得为别人指路。

 我们都知道写好代码后编译器会帮助我们把代码生成可执行程序细加了解又会知道程序的生成又分为四步预处理、编译、汇编、链接。那么这四步具体都在干什么

本文目录

程序翻译之四个步骤

预处理阶段进行宏替换等

编译阶段生成汇编

 汇编生成机器可识别代码

链接生成可执行文件或库文件


程序翻译之四个步骤

我们本文以C语言讲解

在程序生成过程中每一个C语言源文件都会通过编译过程分别转化为目标文件(.obj)这些目标为文件会由链接器捆绑在一起形成一个单一完整的整体链接器同时也会引入标准C函数库中任何被该程序所用到的函数且可以搜索程序员个人的程序库将其需要的函数也链接到程序中形成可执行程序。

思维导图

程序的翻译过程

 注Linux命令中加-o指定输出文件名自定义该文件为可执行文件不加-o会默认生成a.out。 gcc/g++是一个编译器 常见选项 -c 汇编完成后停止; -E 预处理完成后停止; -S 编译完成后停止 -o 用于指定目标文件名称 -g 生成debug程序。向程序中添加调试符号信息

预处理阶段进行宏替换等

预处理阶段是在正式的编译阶段之前进行。预处理是C语言的一个重要的功能它由预处理程序单独完成当对一个源文件进行编译时系统自动引用预处理程序预处理在源代码编译之前根据内置预处理指令对其进行的一些文本性质的操作对源程序编译之前做一些处理生成扩展的C源程序本质还是C语言代码

  1. 头文件包含如#include "FileName"或者#include 等。 该指令将头文件中的定义统统都加入到它所产生的输出文件中以供编译程序对之进行处理。
  2. 宏替换如 #define a b 对于这种伪指令预编译所要做的是将程序中的所有a用b替换但作为字符串常量的 a则不被替换。还有 #undef则将取消对某个宏的定义使以后该串的出现不再被替换。
  3. 去注释删除源代码中掉注释的过程注释不会被带入到编译阶段
  4. 条件编译如#ifdef#ifndef#else#elif#endif等。 这些伪指令的引入使得程序员可以通过定义不同的宏来决定编译程序对哪些代码进行处理。预编译程序将根据有关的文件将那些不必要的代码过滤掉

图示 

源代码
查看详细信息

 可以看到在进行了预处理之后生成.i文件后文件体积变大了很多代码由几行变成了八百多行并且进行了宏替换去注释等操作。

预处理程序对源程序进行上述“文本替换工作”后输出的文件还是C语言代码中将不再包含宏定义、文件包含、条件编译等指令源文件相比功能相同形式不一样。

编译阶段生成汇编

编译阶段gcc会将C语言代码转换成汇编代码文件.s)。在这个阶段中,gcc 首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,gcc 把代码翻译成汇编语言。

  • 编译过程为 扫描程序-->语法分析-->语义分析-->源代码优化-->代码生成器-->目标代码优化
  • 扫描程序进行词法分析从左向右从上往下扫描源程序字符识别出各个单词确定单词类型
  • 语法分析是根据语法规则将输入的语句构建出分析树或者语法树也就是我们答案中提到的分析树parse tree或者语法树syntax tree
  • 语义分析是根据上下文分析函数返回值类型是否对应这种语义检测可以理解语法分析就是描述一个句子主宾谓是否符合规则而语义用于检测句子的意思是否是正确的
  • 目标代码生成指的是把中间代码变换成为特定机器上的低级语言代码。

图示

 我们可以看到在执行命令后我们生成了一个汇编文件将C语言源代码转化成了汇编代码

 汇编生成机器可识别代码

汇编过程是将汇编语言代码翻译生成目标文件(.obj)-二进制文件的过程。目标文件由机器码组成。通常它至少有代码段和数据段两部分。前者包含程序指令后者存放程序中用到的各种全局/静态数据。

图示

在这里可以看到生成的文件是二进制文件显示一片乱码我们完全看不明白。这是因为汇编过程还会生成一个符号表因为这个二进制文件是elf格式的我们可以通过Linux的readelf工具来阅读二进制文件

汇编阶段生成了可重复定位目标二进制文件此文件是不可以被执行的同时通过符号表打印件我们可以看到一个类似表格的东西里面有mainprintf等符号这个就是生成的符号表 在符号表中程序源代码中的每个标识符都和它的声明或使用信息绑定在一起比如其数据类型、作用域以及内存地址。

链接生成可执行文件或库文件

链接阶段将我们生成的.obj文件和库文件某种合并生成可执行程序。链接程序要解决外部符号访问地址问题就是将一个文件中引用的符号与该符号在另一个文件中的定义连接起来从而使有关的目标文件连成一个整体最终成为可被操作系统执行的可执行文件。

在这里涉及到一个重要的概念 : 函数库
我们的C程序中并没有定义“printf”的函数实现,且在预编译中包含的“stdio.h”中也只有该函数的声明,而没有定义函数的实现,那么,是在哪里实“printf”函数的呢?
最后的答案是 : 系统把这些函数实现都被做到名为 libc.so.6 的库文件中去了 , 在没有特别指定时 ,gcc 会到系统默认的搜索路径“/usr/lib” 下进行查找 , 也就是链接到 libc.so.6 库函数中去 , 这样就能实现函数“printf” , 而这也就是链接的作用。

当你们在安装VS2019、VS2022的时候实际上最重要的一个工作是什么呢就是帮我们下载并安装语言的头文件和库文件 

其实我们用的Linux指令命令有相当一部分就是用C写的如何看到指令呢通过linux命令ldd命令用于打印程序或者库文件所依赖的共享库列表可以查看指令就是程序。

函数库一般分为静态库和动态库两种
  • 静态库指编译链接时,把静态库文件中我们所需要的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了。其后缀名一般为“.a”
  • 动态库指编译链接时拷贝动态库中我们所需要的代码的地址到我们可执行文件中的相关位置在程序执行时由运行时链接文件加载库,这样可以节省系统的开销。动态库一般后缀名为“.so”,如 libc.so.6 就是动态库。gcc 在编译时默认使用动态库。完成了链接之后,gcc 就可以生成可执行文件。
  • 优缺点
  • 静态库链接成功后我们的程序不依赖任何库自己可以独立运行但是因为自身拷贝问题比较浪费内存空间
  • 动态库链接成功后我们的程序还是依赖动态库共享程序内部只有地址比较节省内存空间但是一旦动态库缺失我们的程序将无法运行
  • 对于Linux: 静态库 vs 动态库Linux中默认使用的事动态链接和动态库
  • 二者本质区别一个是拷贝内容一个是拷贝地址

 本节完


阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: linux