C语言基础2-解答读effective C++产生的问题

预处理、编译、汇编、链接的区别

预处理、编译、汇编、链接的区别

预处理器(cpp)

C/C++的预处理器是最低端的一种—–词法预处理器,主要是进行文本替换、宏展开、删除注释、把头文件内容包含进来这类简单工作。

gcc -E选项可得到预处理后的结果,预处理结果会显示到屏幕上,如果需要保存,则得需要重定向,扩展名为 .i;
C/C++预处理不做任何语法检查,不仅是因为它不具备语法检查功能,也因为预处理命令不属于C/C++语句(这也是定义宏时不要加分号的原因),语法检查是编译器要做的事情;
预处理之后,得到的仅仅是真正的源代码,输出一个hello world都有好几百行代码;
GCC确实很强大,如果是用VC这种IDE,恐怕就不能看到预处理后的结果。

编译器(ccl)

将文本文件 .i 翻译成文本文件.s,得到汇编语言程序(把高级语言翻译为机器语言),该种语言程序中的每条语句都以一种标准的文本格式确切的描述了一条低级机器语言指令。

gcc -S 选项可以得到编译后的汇编代码,扩展名为 .s;
汇编语言为不同高级语言的不同编译器提供了通用的输出语言,比如,C编译器和Fortran编译器产生的输出文件用的都是一样的汇编语言。

汇编(as)

将 .s 翻译成机器语言指令,把这些指令打包成一种叫做可重定位目标程序的格式,并将结果保存在目标文件.o中(把汇编语言翻译成机器语言的过程)。

gcc -c 选项可以得到汇编后的结果,扩展名为.o;
.o是一个二进制文件,它的字节编码是机器语言指令而不是字符。如果在文本编辑器中打开.o文件,看到的将是一堆乱码;
把一个源程序翻译成目标程序的工作过程分为五个阶段:词法分析、语法分析、语义检查和中间代码生成、代码优化和目标代码生成。主要是进行词法分析和语法分析,又称为源程序分析,分析过程中发现有语法错误,给出提示信息。

链接(ld)

gcc会到系统默认的搜索路径/usr/lib下进行查找,也就是链接到libc.so.6库函数中去。 函数库一般分为静态库和动态库两种

静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了。其后缀名一般为.a;
动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库,这样可以节省系统的开销。动态库一般后缀名为.so,如前面所述的libc.so.6就是动态库。gcc在编译时默认使用动态库。

动态链接不将所有的第三方库都打包到最终的可执行文件上,而是只记录用到了哪些动态链接库,在运行时才将那些第三方库装载(Load)进来。装载是指将磁盘上的程序和数据加载到内存上。例如下图中的Program 1,系统首先加载Program 1,发现它依赖libx.so后才去加载libx.so。
无论何种操作系统上,使用动态链接生成的目标文件中凡是涉及第三方库的函数调用都是地址无关的。假如我们自己编写的程序名为Program 1,Program 1中调用了C标准库的printf(),在生成的目标文件中,不会立即确定printf()的具体地址,而是在运行时去装载这个函数,在装载阶段确定printf()的地址。这里提到的地址指的是进程在内存上的虚拟地址。动态链接库的函数地址在编译时是不确定的,在装载时,装载器根据当前地址空间情况,动态地分配一块虚拟地址空间。

而静态链接库其实是在编译时就确定了库函数地址。比如,我们使用了printf()函数,printf()函数对应有一个目标文件printf.o,静态链接时,会把printf.o链接打包到可执行文件中。在可执行文件中,printf()函数相对于文件头的偏移量是确定的,所以说它的地址在编译链接后就是确定的。

相比之下,动态链接主要有以下好处:
多个可执行文件可以共享使用系统中的共享库。每个可执行文件都更小,占用的磁盘空间也相对比较小。而静态链接把所依赖的库打包进可执行文件,假如printf()被其他程序使用了上千次,就要被打包到上千个可执行文件中,这样会占用了大量磁盘空间。
共享库的之间隔离决定了共享库可以进行小版本的代码升级,重新编译并部署到操作系统上,并不影响它被可执行文件调用。静态链接库的任何函数有了改动,除了静态链接库本身需要重新编译构建,依赖这个函数的所有可执行文件都需要重新编译构建一遍。
当然,共享库也有缺点:
如果将一份目标文件移植到一个新的操作系统上,而新的操作系统缺少相应的共享库,程序将无法运行,必须在操作系统上安装好相应的库才行。
共享库必须按照一定的开发和升级规则升级,不能突然重构所有的接口,且新库文件直接覆盖老库文件,否则程序将无法运行。

↑选段来自↓
Linux的so文件到底是干嘛的?浅析Linux的动态链接库

编译器做了什么

第一步 配置(configure) 第二步 确定标准库和头文件的位置 第三步 确定依赖关系 第四步 头文件的预编译(precompilation)为了节省时间,编译器会在编译源码之前,先编译头文件。这保证了头文件只需编译一次,不必每次用到的时候,都重新编译了 第五步 预处理(Preprocessing) 第六步 编译(Compilation) 第七步 连接(Linking) 第八步 安装(Installation) 第九步 操作系统连接 第十步 生成安装包 第十一步 动态连接(Dynamic linking)
编译器的工作过程

预编译做了什么
头文件的预编译(precompilation)不同的源码文件,可能引用同一个头文件(比如stdio.h)。编译的时候,头文件也必须一起编译。为了节省时间,编译器会在编译源码之前,先编译头文件。这保证了头文件只需编译一次,不必每次用到的时候,都重新编译了。

不过,并不是头文件的所有内容,都会被预编译。用来声明宏的#define命令,就不会被预编译

第五步 预处理(Preprocessing)
预编译完成后,编译器就开始替换掉源码中bash的头文件和宏

什么情况下要使用条件编译呢
条件编译的用法

↓以下内容来自此链接
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_42914662/article/details/126443618