写在前面
最近在网上找各种帖子学习CMakeLists,但是都感觉学得不得劲,所以写下此篇文章,但是也是刚学习,难免会有不正确的地方,仅以此作为笔记记录。
编译过程
无论我们使用的是Windows系统还是Ubuntu系统,我们平时所需要写的代码都在对应的IDE中点击运行即可生成结果,导致现在很多人对C++的编译过程只是停留在背诵的内容上(我就深受其害),因此对CMakeLists的理解必定不到位,所以我觉得先从编译过程开始了解更有助于后面理解。
先贴一张网上到处都有的流程图:
这张图其实就很清楚地说明了问题:首先我们编写的代码是.c或者.cpp文件格式的,这是我们的源代码,进行编译的第一步就是进行预处理,当然这张图没有注明,这也是编译过程中的一部分。
预处理
所谓预处理就是处理一些#内容,比如include命令,define命令,去除一些注释,以及一些特殊符号。这也就是为什么大家在C语言课程中会听到老师说#后面的字符一般不是关键字,而称之为预处理指令,同时也是其不加#报错的原因。预处理完之后的“源代码”仍然是.cpp和.c文件,但是其内容全部变成了没有宏定义,没有特殊符号,没有注释,变成了一个“程序可读性几乎为零”的代码。
编译
然后就是进行编译了,编译就是将经过预处理的代码文件进行分析,这一步就会检查有没有语法问题了,这也是为什么一般大佬在检查程序语法的时候会选择编译而不是运行。检查无误后,就会将其代码翻译成对应的汇编代码,当然这里面还有关于代码优化的问题,这里跳过。汇编代码相对来说比较低级(可不是不好的意思),它接近于机器语言,但是人类可读。我用GPT写了一个输出“hello world”的程序:
section .data
hello db 'Hello, World!', 0x0A ; 定义一个字符串,并以换行符结尾
section .text
global _start
_start:
; 写操作,系统调用号为 1 (sys_write)
mov eax, 4 ; 系统调用号 4: sys_write
mov ebx, 1 ; 文件描述符 1: 标准输出 (stdout)
mov ecx, hello ; 将要输出的字符串地址
mov edx, 14 ; 字符串长度 (13 个字符 + 1 个换行符)
int 0x80 ; 调用内核
; 退出程序,系统调用号为 1 (sys_exit)
mov eax, 1 ; 系统调用号 1: sys_exit
xor ebx, ebx ; 退出状态 0
int 0x80 ; 调用内核
不客气地说我能看懂int
,以及……没有以及。
汇编
刚才说到编译后的代码就是汇编代码,汇编代码进入汇编器后,就成了目标代码,目标代码那就是传说中的机器指令编码了,是二进制格式文件,属于极其ZB的存在了,我也用GPT写了一个和上面对应的:
hello.o: file format elf32-i386
Disassembly of section .text:
00000000 <_start>:
0: b8 04 00 00 00 mov $0x4,%eax
5: bb 01 00 00 00 mov $0x1,%ebx
a: b9 00 00 00 00 mov $0x0,%ecx
f: ba 0e 00 00 00 mov $0xe,%edx
14: cd 80 int $0x80
16: b8 01 00 00 00 mov $0x1,%eax
1b: 31 db xor %ebx,%ebx
1d: cd 80 int $0x80
这就是属于机器可读的代码了,可以称之为机器语言。
链接
那么问题来了,都机器可读了,为啥.o文件不是可执行程序?我们在写C++程序的时候肯定会不可避免的调用其他库,另外一般一个稍微大一点的程序也不会只有一个.cpp文件,等各种情况,通过上面的汇编我们将一个个代码翻译成了机器语言,但是,各个源文件之间还相互独立,符号引用和外部依赖还没解决。或者换个表达方式——报错:“未解析的符号和外部依赖”,(听懂掌声,我就不信学C++的不知道这个)。因此这就需要最后一步建立这些文件间的关系——链接。
链接也分为两种,静态链接和动态链接,首先声明,二者没有高级低级之分,只有在不同情况下的实用性。两种方式都是处理程序代码和库代码之间的关系。
静态链接
静态链接就是直接把库文件内容全盘粘贴上去,举个例子,我想实现一个C++的加法程序,相加函数太难不会写,直接找到网上有的函数直接一手C+V,这不是抄袭,这是静态链接!这样有啥好处呢?他不依赖库文件,库文件不管怎么动都和我没关系,就像这个代码是我自己写的一样。缺点就是不好维护,假如我写了100个加法程序,我突然意识到那个函数有个地方写错了,我就得改100次,另外,每个程序都有他自己的静态链接,全是拷贝的文件,因此他占用的内存也更大。
动态链接
所谓动态链接就是不同程序共享相同的代码段,我印象嘎嘎深。以前课题组有一个C#程序,需要我进行一定的维护,有一次我电脑重装系统了,因为是我自己装的,所以就没装Office,Office有一个动态库叫Mgd.dll(也可能是叫其他名字),好巧不巧,这个程序用到了Office,然后我运行一直报错找不到这个dll,最后我还是翻代码才弄清楚了。上面Office和C#程序都用到了这个dll(当然Ubuntu里面的动态链接库不叫dll,叫.so),可以这样理解,电脑里面只有一个这样子的文件,然后两个程序在文件中包含了这个库的注册信息,当调用的时候拿着信息满盘的找他,找到了即调用成功。因此,哪怕我写100个程序,我也只需要一个库,每个程序只需要留一段他的注册信息即可,这样便节省了大量空间,同时维护也只需要修改库即可,无需变动各个程序。当然损耗的就是性能,毕竟要拿着门牌号去找。
经过链接器之后,得到的程序就是我们最后需要的可执行程序了,也就是windows下的.exe,ubuntu下的话未必会有后缀或固定后缀。
以上就是一个C++源码程序编译的整个过程,不过解释型的语言例如python的运行流程一般情况下就会有很大不同。
总结
这次写了很多,但感觉质量可能不高,里面可能会有很多表达不够严谨甚至表达不正确的地方。