用gcc编译程序时分为4个步骤:
预处理(Prepressing): 把 .c 生成 .i
编译(Compliation): 把 .i 生成 .s
汇编(Assembly): 把 .s 生成 .o
链接(Linking): 把 .o 生成 目标文件
1、预处理
主要处理源代码中以"#"开始的预编译指令,比如"#include"、"#define",相当于如下命令:
gcc -E test.c -o test.i
主要规则如下:
(1)将所有的“#define”删除,并且展开所有的宏定义;
(2)处理所有条件编译指令,比如“#if”、“#ifdef”、“#elif”、“#else”、“#endif”;
(3)处理“#include”预编译指令,将被包含的文件加入到该项编译指令的位置;
(4)删除所有注释;
(5)添加行号和文件标识,以便于编译时编译器产生调试的行号信息及用于编译时产生编译错误和警告是能够显示行号;
(6)保留所有“#pragma”编译指令,因为编译器需要使用它们;
经过预编译后的 .i 文件不包含任何宏定义;并且包含的文件也已经被插入到 .i 文件中。所以当我们无法判断宏定义是否正确或头文件包含是否正确时,也可以 查看预处理后的文件来确认问题。
2、编译
把预处理完的文件进行一系列的词法分析、语法分析、语义分析以及优化后产生相应的的汇编代码,相当于如下命令:
gcc -S test.i -o test.s
现在版本的gcc把预处理和编译合并成一个步骤,使用一个叫ccl的程序来完成连个步骤:
gcc -S test.c -o test.s
c++的对应程序叫做cclplus。
所以实际上gcc这个命令指示这些后台程序的包装,他会根据不同的参数要求调用 预处理编译程序ccl、汇编器as、链接器ld。
3、汇编
将汇编代码转变成机器可以执行的指令,每一个汇编语句几乎都对应一条机器指令,所以汇编器的编译过程相对于编译器来讲比较简单,他没有复杂的语法,也没有语义,也不需要做指令优化,只是根据汇编指令和机器指令的对照表一一翻译就可以了。
上面的汇编过程我们可以调用汇编器as来完成:
as test.s -o test.o 或者 gcc -c test.s -o test.o
或者使用gcc命令从C源码文件开始,经过预处理、编译、汇编直接输出目标文件:
gcc -g test.c -o test.o
4、链接
将一个个目标文件(或许还会有若干程序库)链接在一起生成一个完整的可执行文件。人们把每个源代码模块独立地编译,然后按照要求把它们“组装”起来,这个组装模块的过程就叫做链接。
库其实就是一组目标文件包,就是一些最常用的代码编译成目标文件后打包存放。
静态链接库(lib文件)
静态链接就是在编译链接时直接将需要执行的代码拷贝到调用处。
优点:
(1)程序发布的时候不需要依赖库,也就是不再需要带着库一起发布,可执行文件可以独立执行;
(2)代码的装载速度快,执行速度也比较快,因为编译器只会把你需要的那部分链接进去;
缺点:
(1)可执行文件的体积会变大一些;
(2)如果多个应用程序都要使用同一个静态库的话,会被加载多次,浪费内存;
动态链接库(dll文件)
动态链接就是在编译的时候不直接拷贝代码,而是通过一系列符号和参数,在程序运行或加载时将这些信息操作系统,操作系统负责将需要的动态库加载到内存中,然后程序在运行到指定代码时,去执行内存中已经加载的动态库可执行代码,最终达到运行时链接的目的。
优点:
(1)多个应用程序可以使用同一个动态库,启动多个应用程序的时候,只需要将动态库加载到内存一次即可,节约内存;
(2)极大的提高了程序的可维护性和可扩展性,只要输出接口不变,更换dll文件不会对可执行文件产生影响;