Linux下生成可执行程序的每一步过程以及链接库的初步认识

程序的翻译

程序在形成可执行程序之前都经历过一系列十分复杂的过程,也就是我们程序的翻译,程序的翻译经过以下阶段:

  1.  预处理(进行宏替换)
  2.  编译(生成汇编)
  3.  汇编(生成机器可识别代码)
  4.  连接(生成可执行文件或库文件)

就以C语言代码为例,当我们写好了一份C语言代码,第一部要进行头文件的包含,然后在主函数下咔咔一顿写,再开始翻译,也就是形成可执行程序的过程.

我们要了解翻译过程,大致是要将我们写的代码转换成汇编,然后将汇编形成二进制码,也就是计算机可以识别的代码,最后将目标二进制文件和链接库通过链接器连接生成可执行程序。

 预处理阶段

预处理执行的就是:1.将文件的头文件展开 2.宏替换 3.条件编译 4.代码注释的删除

而头文件展开是干啥呢,这里就要大略的谈谈头文件和库文件了,头文件就是我们的stdio.h,而我们包含的这个头文件功能起到一些函数、变量类型定义的作用,而函数的具体实现是在库文件里的,例如我们的printf函数也是需要实现的,就是在stdio.h定义的,如果我们要使用函数的话,编译器就需要到特定的库文件中去寻找该函数的实现,而库文件的内容实质上都是一些二进制。所以回到问题,头文件展开的实质就是将stdio.h里面的内容拷贝一份放在咱们的代码里。

宏替换就比较简单,就是将#define定义的宏变量,宏函数等内容进行替换。

条件编译就是将#if #else #endif等内容的执行

代码注释删除就不解释了哈

linux下我们编译C语言代码时可以用gcc编译器,编写C++代码时可以用g++编译器,而且这些编译器还可以执行到具体某个过程终止:例如想让代码就执行完预处理过程就终止的话可以 gcc -E test.c -o test.i //其实-E是指预处理过程,而-o是预处理的文件存放在test.i的文件中

test.c代码

 预处理之后:

其实并不仅仅只有这些,还有头文件展开的内容,由于内容过于丰富所以就未截取。 

编译

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

  • 编译过程为 扫描程序-->语法分析-->语义分析-->源代码优化-->代码生成器-->目标代码优化;
  • 扫描程序进行词法分析,从左向右,从上往下扫描源程序字符,识别出各个单词,确定单词类型
  • 语法分析是根据语法规则,将输入的语句构建出分析树parse tree或者语法树syntax tree
  • 语义分析是根据上下文分析函数返回值类型是否对应这种语义检测,可以理解语法分析就是描述一个句子主宾谓是否符合规则,而语义用于检测句子的意思是否是正确的
  • 目标代码优化就是执行死代码删除、函数内联、for循环的循环控制变量调度到寄存器访问、强度削弱...(而死代码的删除并不是注释删除而是移除根本执行不到的代码,或者对程序运行结果没有影响的代码    内联函数,也叫编译时期展开函数, 指的是建议编译器将内联函数体插入并取代每一处调用函数的地方,从而节省函数调用带来的成本,使用方式类似于宏,但是与宏不同的是内联函数拥有参数类型的校验,以及调试信息,而宏只是文本替换而已    for循环的循环控制变量,通常被cpu访问频繁,因此如果调度到寄存器中进行访问则不用每次从内存中取出数据,可以提高访问效率     强度削弱是指执行时间较短的指令等价的替代执行时间较长的指令,比如 num % 128 与 num & 127 相较,则明显&127更加轻量)

上面这块内容为摘抄大佬文案,我也不太懂,看看猪肉就行了。

而这一过程的指令就是:gcc -S test.i -o test.s//这里其实-S后面跟test.c也行,只不过我们刚刚已经执行了预处理过程,紧接着就是编译,所以直接对test.i操作即可。

这其实就是编译的过程:形成汇编代码。

汇编

汇编就是将编译阶段形成的汇编代码转换成二进制文件,此时就可以被计算机识别。

而这一过程的指令就是:gcc -c test.s -o test.o//这里其实-c后面跟test.c也行,只不过我们刚刚已经执行了编译过程,紧接着就是汇编,所以直接对test.i操作即可。

这里是部分截图,而且需要查看该二进制码的指令是od,如果一般的查看方式的话是一堆乱码。

链接

链接就是将目标文件(test.o)与链接库通过链接器链接起来的过程。

而这里主要就是了解一下什么是链接库。链接库也划分成静态库和动态库两种。

链接库

在我们实现C语言程序的时候,一般是要在开头包含头文件的,而对于头文件的作用就是声明一些你用到的函数,就像printf函数,但是函数的定义(实现)在哪里呢,其实就是在库文件(libc.so.6
)中,而在使用的过程中系统会默认到该路径下去寻找,这样就有函数的定义的,也就可以正常使用了。

静态库

静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了。其后缀名一般为“.a”

就是静态库进入.o的二进制目标文件中,链接成可执行程序的时候,链接器会复制静态库内的函数和数据进入可执行程序中
 

动态库

动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库,这样可以节省系统的开销。动态库一般后缀名为“.so”,如前面所述的 libc.so.6 就是动态库。gcc 在编译时默认使用动态库。完成了链接之后,gcc 就可以生成可执行文件。

猜你喜欢

转载自blog.csdn.net/C_Rio/article/details/132836056
今日推荐