程序编译的四部曲

提到编译我们并不陌生,静态编译、动态编译等相关概念也经常出现,那么编译到底是什么,编译的过程又是如何呢?其实我们平时提到的编译,仅仅是编译过程中的一部分,真正的编译过程可总结为如下四步:

  • 预编译
  • 编译
  • 汇编
  • 连接

预编译

删除所有#define,展开所有宏定义;删除注释;处理#include等等。

编译

将预处理后的文件进行一系列词法分析、语法分析以及优化等。此步骤需要.h文件,换言之,编译器需要知道,我程序所用的方法都是声明过的,都是合法的!

汇编

程序经过预编译、编译、汇编,此时已经输出目标文件(win下的.obj,Linux下的.o)。

链接

链接即是将之前生成的目标文件以及我们的库进行链接,生成可执行程序的过程,这里的可执行程序所指范围很广,可以是exe、lib、dll 等。此步骤所需文件为.lib文件以及上一步生成的目标文件,用其进行重定位!此步骤中静态编译、动态编译有很大区别,下面我们介绍下这两种模式的区别。

静态编译

链接过程

进行重定位,即在.lib文件中找到我们所需内容(并不是将所有指令拷贝)嵌入到对应目标文件中,和目标文件一起打包生成为最后的可执行程序。此时可执行程序可能会很大。

生成和使用

编译静态库仅会生成.lib文件,无需其他支持。
使用此静态库,链接过程中需要.lib文件,一旦编译完毕,即打包成最终可执行程序,运行时则无需此.lib文件。

动态编译

链接过程

.lib文件仅仅为索引文件,真正的核心内容都在dll中,链接过程仅仅用.lib文件进行重定位。个人理解此重定位是将目标文件中调用的函数(动态库中)的代码,替代为.lib里所存储的此函数在dll里的索引位置,一起打包为最后的可执行程序。这样在程序运行时,程序可以顺利的解析dll(找到相应方法在dll中的正确地址),从而使程序顺利的进行!

生成和使用

编译动态库最终生成dll,lib,exp等等文件。lib为索引文件,dll为核心指令文件,exp是一个中间文件,我们可不予理会。
使用此动态库,链接过程需要.lib文件,运行时需要dll文件。

动态导出/导入符

__declspec(dllexport) __declspec(dllimport)顾名思义,这俩符号是动态库导出时候才用到的。如果主程序调用静态库,包含的头文件还加__declspec(dllimport),这样会导致直接编译不过。

  • __declspec(dllexport)这个符号非常重要,假如不加,我们动态编译后,生成的dll会有问题,而且还缺少lib和exp等文件,没有lib文件,意味着主程序想要使用这个动态库,则四部曲中的链接步骤无法进行。
  • __declspec(dllimport)这个符号相对于导出,就显得不重要了。这个符号会出现在供主程序包含的.h文件中,假如没有此符号,编译主程序时大多数不受影响!仅当我们的导出方法中含有static方法时,则必须加此符号,否则会导入失败。

为了使我们头文件用同一版本,我们只需定义一个通用宏替代本身导入导出符号即可,具体做法如下:

1
2
3
4
5
#if defined(MOUDLE_LIBRARY)
# define MOUDLE_EXPORT __declspec(dllexport)
#else
# define MOUDLE_EXPORT __declspec(dllimport)
#endif

这样在本项目中,我们只需定义宏MOUDLE_LIBRARY,此时MOUDLE_EXPORT即为导出符号。当其他项目使用此库,包含其头文件时,由于没有定义此宏,则MOUDLE_EXPORT即为导入符号。

附带提一句,在VS中,编译时关于对C++基本库的使用是动态还是静态,我们要想将其打包进我们的程序中,可以在选项中调节(MT/MD等选项)。