c++ 编译过程引入
常见:
用gcc、g++指令生成可执行文件,对于大点的项目,则是:
$ ./configure
$ make
$ make install
这背后。到底发生了什么?
当然,纯粹的编译过程就是老生常谈的编译四部曲,configure这个也可以理解成一种软件构件过程,但毕竟和编译息息相关,说白了configure这一套操作就是帮助编译的管理,所以就放一起谈也能解惑不少
以下主要参考:阮一峰大神的编译器的工作过程http://www.ruanyifeng.com/blog/2014/11/compiler.html
Linux编译安装中configure、make和make install各自的作用详解:
https://blog.csdn.net/qq_27825451/article/details/102605219
看看这些命令
https://blog.csdn.net/stone_fall/article/details/108602898
configure干的事情
第一步 配置(configure)
编译器在开始工作之前,需要知道当前的系统环境,比如标准库在哪里、软件的安装位置在哪里、需要安装哪些组件等等。这是因为不同计算机的系统环境不一样,通过指定编译参数,编译器就可以灵活适应环境,编译出各种环境都能运行的机器码。这个确定编译参数的步骤,就叫做"配置"(configure)。
这些配置信息保存在一个配置文件之中,约定俗成是一个叫做configure的脚本文件。通常它是由autoconf工具生成的。编译器通过运行这个脚本,获知编译参数。
configure脚本已经尽量考虑到不同系统的差异,并且对各种编译参数给出了默认值。如果用户的系统环境比较特别,或者有一些特定的需求,就需要手动向configure脚本提供编译参数。
$ ./configure --prefix=/www --with-mysql
上面代码是php源码的一种编译配置,用户指定安装后的文件保存在www目录,并且编译时加入mysql模块的支持。
第二步 确定标准库和头文件的位置
源码肯定会用到标准库函数(standard library)和头文件(header)。它们可以存放在系统的任意目录中,编译器实际上没办法自动检测它们的位置,只有通过配置文件才能知道。
编译的第二步,就是从配置文件中知道标准库和头文件的位置。一般来说,配置文件会给出一个清单,列出几个具体的目录。等到编译时,编译器就按顺序到这几个目录中,寻找目标。
第三步 确定依赖关系
对于大型项目来说,源码文件之间往往存在依赖关系,编译器需要确定编译的先后顺序。假定A文件依赖于B文件,编译器应该保证做到下面两点。
(1)只有在B文件编译完成后,才开始编译A文件。
(2)当B文件发生变化时,A文件会被重新编译。
编译顺序保存在一个叫做makefile的文件中,里面列出哪个文件先编译,哪个文件后编译。而makefile文件由configure脚本运行生成,这就是为什么编译时configure必须首先运行的原因。
在确定依赖关系的同时,编译器也确定了,编译时会用到哪些头文件。
然后是第四步编译过程了,这也是make命令的作用,这里编译过程我单独截取处理放到后面再谈
第五步安装和操作系统链接
编译完成即make之后,就是安装了,编译生成了可执行文件,但如果要做的方便使用,何妨把一些可预知的步骤做完。
这也是make install的作用了:
表面上,这一步很简单,就是将可执行文件(连带相关的数据文件)拷贝过去就行了。但是实际上,这一步还必须完成创建目录、保存文件、设置权限等步骤。这整个的保存过程就称为"安装"(Installation)。
可执行文件安装后,必须以某种方式通知操作系统,让其知道可以使用这个程序了。比如,我们安装了一个文本阅读程序,往往希望双击txt文件,该程序就会自动运行。
这就要求在操作系统中,登记这个程序的元数据:文件名、文件描述、关联后缀名等等。Linux系统中,这些信息通常保存在/usr/share/applications目录下的.desktop文件中。另外,在Windows操作系统中,还需要在Start启动菜单中,建立一个快捷方式。
这些事情就叫做"操作系统连接"。make install命令,就用来完成"安装"和"操作系统连接"这两步。
第六步 生成安装包
这步开始还么搞懂,什么生成安装包了?,,
写到这里,源码编译的整个过程就基本完成了。但是只有很少一部分用户,愿意耐着性子,从头到尾做一遍这个过程。事实上,如果你只有源码可以交给用户,他们会认定你是一个不友好的家伙。大部分用户要的是一个二进制的可执行程序,立刻就能运行。这就要求开发者,将上一步生成的可执行文件,做成可以分发的安装包。
所以,编译器还必须有生成安装包的功能。通常是将可执行文件(连带相关的数据文件),以某种目录结构,保存成压缩文件包,交给用户。
第七步 可能存在的动态链接
正常情况下,到这一步,程序已经可以运行了。至于运行期间(runtime)发生的事情,与编译器一概无关。但是,开发者可以在编译阶段选择可执行文件连接外部函数库的方式,到底是静态连接(编译时连接),还是动态连接(运行时连接)。所以,最后还要提一下,什么叫做动态连接。
前面已经说过,静态连接就是把外部函数库,拷贝到可执行文件中。这样做的好处是,适用范围比较广,不用担心用户机器缺少某个库文件;缺点是安装包会比较大,而且多个应用程序之间,无法共享库文件。动态连接的做法正好相反,外部函数库不进入安装包,只在运行时动态引用。好处是安装包会比较小,多个应用程序可以共享库文件;缺点是用户必须事先安装好库文件,而且版本和安装位置都必须符合要求,否则就不能正常运行。
现实中,大部分软件采用动态连接,共享库文件。这种动态共享的库文件,Linux平台是后缀名为.so的文件,Windows平台是.dll文件,Mac平台是.dylib文件。
老生常谈的编译四部曲
c++ 编译分为四步,编译预处理、编译、汇编、链接。
阮一峰大神这里有个头文件的预编译剥离开,倒是不一样的细节。有的文章直接把预编译和预处理放一块,亦或者称预处理也是预编译,无所谓了,总而言之是编译的前奏。
预处理(Preprocessing)
预处理又称为预编译,是做些代码文本替换工作。编译器执行预处理指令(以#开头,例如#include),这个过程会得到不包含#指令的.i文件。这个过程会拷贝#include 包含的文件代码,进行#define 宏定义的替换 , 处理条件编译指令 (#ifndef #ifdef #endif)等。
预处理的总结,还未写
编译、汇编
这里也是,在阮一峰大神的文章中说,编译可看成两步,由编译得到汇编码,然后再有汇编码得到机器码,这里其实也无所谓,对于我们理解编译过程来说,只要最后知道是机器码就好了
编译得到汇编码,但其中设计很多优化工作,如删除公共表达式、循环优化(代码外提、强度削弱、变换循环控制条件、已知量的合并等)、复写传播,以及无用赋值的删除,等等,对于硬件,如何充分利用机器的各个硬件寄存器存放有关变量的值,以减少对于内存的访问次数,具体不展开
总的来说,编译程序所要作得工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码。
汇编过程实际上指把汇编语言代码翻译成目标机器指令的过程。对于被翻译系统处理的每一个C语言源程序,都将最终经过这一处理而得到相应的目标文件。目标文件中所存放的也就是与源程序等效的目标的机器语言代码。
链接
由汇编程序生成的目标文件并不能立即就被执行,其中可能还有许多没有解决的问题。
例如,某个源文件中的函数可能引用了另一个源文件中定义的某个符号(如变量或者函数调用等);在程序中可能调用了某个库文件中的函数,等等。所有的这些问题,都需要经链接程序的处理方能得以解决。
链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体。
根据开发人员指定的同库函数的链接方式的不同,链接处理可分为两种:
- 静态链接
在这种链接方式下,函数的代码将从其所在的静态链接库中被拷贝到最终的可执行程序中。这样该程序在被执行时这些代码将被装入到该进程的虚拟地址空间中。静态链接库实际上是一个目标文件的集合,其中的每个文件含有库中的一个或者一组相关函数的代码。
- 动态链接
在此种方式下,函数的代码被放到称作是动态链接库或共享对象的某个目标文件中。链接程序此时所作的只是在最终的可执行程序中记录下共享对象的名字以及其它少量的登记信息。在此可执行文件被执行时,动态链接库的全部内容将被映射到运行时相应进程的虚地址空间。动态链接程序将根据可执行程序中记录的信息找到相应的函数代码。
对于可执行文件中的函数调用,可分别采用动态链接或静态链接的方法。使用动态链接能够使最终的可执行文件比较短小,并且当共享对象被多个进程使用时能节约一些内存,因为在内存中只需要保存一份此共享对象的代码。但并不是使用动态链接就一定比使用静态链接要优越。在某些情况下动态链接可能带来一些性能上损害。
这里浅谈一下理解,其实对于怎么用静态库和动态库而言,无非就是,生成可执行文件时,链接的时候,静态库动态库都得有,生成完之后,静态库可以不要了,但是如果链接时用了动态库,那么运行可执行文件时也会需要它,可以看成是一种依赖,其中关系也很好理解。
关于动态链接库、静态链接库
静态库(.a、.lib)和动态库(.so、.dll)。 windows上对应的是.lib .dll linux上对应的是.a .so
主要就是区别,而主要区别在于:
二者的不同点在于代码被载入的时刻不同。
1.静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库,因此体积较大。
2.动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入,因此在程序运行时还需要动态库存在,因此代码体积较小。
动态库和静态库的生成和使用可参考
最后引申一下动态库的显示链接和隐式链接,以及对应的.dll .lib .h文件怎么放,见
与cmake的区别
区别
实际上都是为了生成Makefile文件,然后make编译。
参考:https://www.cnblogs.com/mickole/articles/3659112.html