我们很少关注编译链接过程,因为通常的开发环境都是流行的集成开发环境(IDE),如Visual Studio等。这样的IDE一般都将
编译和链接的过程一步完成,通常将这种编译和链接合并到一起的过程称为构建(Build)。
在Linux下,当我们使用gcc编译main程序时,只需使用最简单的命令——gcc -o main main.c(main为可执行程序的名字,main.c为源文件,源文件可以有多个)一步完成,事实上其编译运行过程可以分解为4个步骤:分别是预处理(Prepressing)、编译(Compilation)、汇编(Assembly)和链接(Linking)。
预处理: gcc -E main.c -o main.i
编译: gcc -o main.s -S main.i
汇编: gcc -c main.s -o main.o
链接: gcc -o main main.o
执行main文件:1、 ./main 执行main文件
2、pwd——>复制的全路径/main
当将要执行的文件处于/bin与/usr.bin之下,则不加全路径。
图 1 gcc编译过程
一、预编译
预编译又称为预处理,是做些代码文本的替换工作,是整个编译过程做先做的工作。
首先是源代码文件main.c和相关的头文件,如stdio.h等被预编译器cpp预编译成一个*.i文件。第一步预编译的过程相当于如下命令(-E表示只进行预编译):
gcc -E main.c -o main.i (-E main.c 与 -o main.i 顺序可变,main.i为生成的文件)
预编译过程主要处理规则如下:
1、将所有的“#define”删除,并且展开所有的宏定义。
2、处理所有条件预编译指令,如“#if”、“#ifdef”、“#elif”、“#else”、“#endif”。
3、处理“#include”预编译指令,将被包含的文件插入到该预编译指令的位置。注意,这个过程是递归进行的,也就是说被包含的文件可能还包含其他文件。
4、删除所有的注释“//”和“* *”。
5、添加行号和文件名标识,比如#2“main.c”2,以便于编译时编译器产生调试用的行号信息及用于编译时产生编译错误或警告时能够显示行号。
6、保留所有的#pragma编译器指令,因为编译器需要使用它们。
经过预编译后的.*i文件不包含任何宏定义,因为所有的宏已经被展开,并且包含的文件也已经插入到.i文件中。所以当我们无法判断宏定义是否正确或头文件包含是否正确时,可以查看预编译后的文件来确定问题。
二、编译
1、利用编译程序从源语言编写的源程序产生目标程序的过程。
2、用编译程序产生目标程序的动作。 编译就是把高级语言变成计算机可以识别的2进制语言,计算机只认识1和0,编译程序把人们熟悉的语言换成2进制的。
编译程序把一个源程序翻译成目标程序的工作过程分为五个阶段:词法分析;语法分析;语义检查和中间代码生成;代码优化;目标代码生成。主要是进行词法分析和语法分析,又称为源分析分析,分析过程中发现有语法错误,给出提示信息。
这个过程往往是整个程序构建的核心部分,也是最复杂的部分之一,-S只进行编译,不进行汇编,生成汇编代码。上面的编译过程相当于如下命令:
gcc -S main.i -o main.s
gcc这个命令只是后台程序的包装,它会根据不同的参数要求去调用预编译程序cc1、汇编器as、链接器ld。
三、汇编
汇编器是将汇编代码*.s文件转变成二进制机器可以执行的指令文件*.o。汇编过程可以调用汇编器as来完成:
as main.s -o main.o
或者:gcc -c main.s -o main.o
汇编阶段是把编译阶段生成的*.s文件转成目标文件。linux中*.o文件运行不了,因为地址还没有确定。
四、链接
由汇编程序生成的目标文件并不能立即就被执行,其中可能还有许多没有解决的问题。例如,某个源文件中的函数可能引用了另一个 源文件中定义的某个符号(如变量或者函数调用等);在程序中可能调用了某个库文件中的函数,等等。所有的这些问题,都需要经链接程序的处理方能得以解决。 链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够诶操作系统装入执行的统一整体。
根据开发人员指定的同库函数的链接方式的不同,链接处理可分为两种:动态链接,静态链接。
进行的操作是从库里实现的拿出来,把缺的东西补齐,若库没有,则显示未定义。
链接过程主要包括了地址和空间分配、符号决议和重定位等这些步骤。
链接相当于如下命令:
gcc -o main main.o
以上四步可以用两步实现生成可执行的main程序文件: gcc -c main.c
gcc -o main.o
若printf的写法错误,则此错误为链接错误。
声明没有实现,或未声明(调用时名字写错)则为链接错误。