程序的翻译过程分为:预处理、编译、汇编、链接
在Linux中,我们可以用gcc命令的各种选项看到翻译的各过程,可以在每个阶段停下来,并且可以看到中间的翻译结果,这样就更便于我们理解翻译过程
gcc -E test.c -o test.i
形成一个test.i文件,文件中保存的是gcc -E产生的临时结果
我们的test.c文件中只有24行,结果test.i中多出800多行,那多出来的这么多是什么呢?
其实这么多代码都是从stdio.h这个头文件展开来的
在预处理阶段,编译器会将我们源代码中所需要的头文件拷贝到源文件中来,我们的头文件中可能也会包含头文件,所以可能会进行递归的拷贝,这个过程叫做头文件展开
在安装编译器的时候,C标准库的头文件一般会一并下载到/usr/include/
我们可以打开stdio.h看一下
对比一下,确实我们展开的是stdio.h
我们可以看到预处理阶段进行了宏替换,注释也被替换了
我们现在下载的软件,大多都分为好几个版本:专业版、社区版、学生版...
那这是怎么维护的呢,如果一个版本有一份源代码,那维护起来的成本是非常大的,这就可以用条件编译来解决这个问题了,只需要维护一份代码,可以用条件编译进行代码的动态裁剪
我们在C语言阶段就有过条件编译的说明:#ifdef #elif #else #endif
具体在《C语言 预处理》专栏的条件编译有说明
C语言 预处理详解-CSDN博客
我们编译成proj.i看一下
我们可以看到,满足条件的保留,不满足条件的删除
我们可以不在文件中宏定义,可以通过gcc -D进行命令行式的宏定义,这样我们就可以动态地向源代码添加宏
预处理的结果是test.i,是一份干净的C语言代码
gcc -S test.i -o test.s
gcc -S产生一个test.s的临时结果
但是这个代码我们可能看不懂,但是我们知道这是汇编语言
gcc -c test.s -o test.o
gcc -c 将test.s文件转成test.o文件,.o表示.obj,在vs中我们编译文件就会产生.obj文件
产生的.obj文件叫做目标文件,这个目标文件不能直接执行,最终形成.exe可执行程序才能运行
gcc test.o -o my.exe
只有最终形成可执行文件,才可以执行
关于链接,我们有三个问题:
链接的过程是我们的程序和库结合的过程
我们的C程序中,并没有定义“printf”的函数实现,且在预编译中包含的“stdio.h”中也只有该函数的声明,而没有定义函数的实现,那么,是在哪里实“printf”函数的呢?
最后的答案是:系统把这些函数实现都被做到名为 libc.so.6 的库文件中去了,在没有特别指定时,gcc 会到系统默认的搜索路径“/usr/lib”下进行查找,也就是链接到 libc.so.6 库函数中去,这样就能实现函数“printf”了,而这也就是链接的作用
库:语言一定要有自己的标准库
我们可以用ldd命令来看到对应的动态库
这就是我们的C标准库
在安装开发环境的时候,会安装C标准库+C头文件,这时候我们才可以包含对应的头文件,调用头文件里声明的函数
函数库一般分为静态库和动态库两种
静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了。其后缀名一般为“.a”
动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库,这样可以节省系统的开销。动态库一般后缀名为“.so”,如前面所述的 libc.so.6 就是动态库。gcc 在编译时默认使用动态库。
链接时,两种链接方式:
动态库和动态链接的优缺点
静态库和静态链接的优缺点
C动态库,是默认提供的
gcc默认形成的可执行程序,默认采用动态链接
而Linux中静态库默认是没有的
安装静态库我们可以用yum指令
sudo yum install -y glibc-static libstdc++-static
这时我们就可以编译通过了
同样运行也能通过
静态链接的应用场景
由于静态链接不依赖于任何的动态库,所以在移植到其他环境中时就不需要做过多的环境检测,可以直接运行,方便部署