gcc详细编译过程
gcc详细编译过程
1. 程序编译4步骤
1.预处理
2.编译
3.汇编
4.链接
我们经常使用“编译”泛指上面的4个步骤之一,甚至有时候会囊括这四个步骤。
对于嵌入式linux开发来说,用到的编程语言有C,C++,汇编。将一个大型程序(包含有操作系统)编译成可执行程序都会经历这四个步骤:
- 预处理器将所有的.c 、.cpp文件处理为.i文件
- 编译器再将所有的.i文件编译为.S文件
- 汇编器再将所有的.S文件汇编为.o文件
- 链接器再将所有的.o文件链接为一个可执行文件
注意:.S文件本质上就是二级制文件(机器码),.o文件也是机器码
2. gcc使用示例
2.1 准备工作
2.1.1 git工具
git工具大家自行准备。
2.1.2 Mingw
Mingw相当于是window版本的gcc,大家自行下载,但是不要忘了添加环境变量。
添加完成后可以使用: gcc -v 命令查看是否环境变量中已经存在
如果出现上图所示的信息就说明已经添加成功了。
2.2 gcc使用语法
gcc [选项] 文件名
gcc hello.c // 输出一个名为a.out的可执行程序,然后可以执行./a.out
gcc -o hello hello.c // 输出名为hello的可执行程序,然后可以执行./hello
gcc -o hello hello.c -static // 静态链接
gcc -c -o hello.o hello.c // 先编译(不链接)
gcc -o hello hello.o // 再链接
2.3 gcc常用选项
2.3.1 手工控制编译过程
选项 | 功能 |
---|---|
-v | 查看gcc编译器的版本,显示gcc执行时的详细过程 |
-o | 指定输出文件名为file,这个名称不能跟源文件名同名 |
-E | 只预处理,不会编译、汇编、链接t |
-S | 只编译,不会汇编、链接 |
-c | 编译和汇编,不会链接 |
一个c/c++文件要经过预处理、编译、汇编和链接才能变成可执行文件。
(1)预处理
C/C++源文件中,以“#”开头的命令被称为预处理命令,如包含命令“#include”、宏定义命令“#define”、条件编译命令“#if”、“#ifdef”等。
预处理就是将要包含(include)的文件插入原文件中、将宏定义展开、根据条件编译命令选择要使用的代码,最后将这些东西输出到一个“.i”文件中等待进一步处理。
预处理不处理语法错误,只是会执行预处理指令(包含头文件,宏定义,预处理条件开关)
(2)编译
编译就是把C/C++代码(比如上述的“.i”文件)“翻译”成汇编代码。
编译的时候编译器会检查语法错误。
(3)汇编
汇编就是将第二步输出的汇编代码翻译成符合一定格式的机器代码,在Linux系统上一般表现为ELF目标文件(OBJ文件)。“反汇编”是指将机器代码转换为汇编代码,这在调试程序时常常用到。
(4)链接
链接就是将上步生成的OBJ文件和系统库的OBJ文件、库文件链接起来,最终生成了可以在特定平台运行的可执行文件。
2.3.2 光说不练假把式
hello.c(预处理)->hello.i(编译)->hello.s(汇编)->hello.o(链接)->hello.exe(windows下)
详细的每一步命令如下:
gcc -E -o hello.i hello.c
gcc -S -o hello.s hello.i
gcc -c -o hello.o hello.s
gcc -o hello hello.o
2.3.2.1 对hello.c文件只进行预处理操作
给出hello.c的源代码,如下:
#include <stdio.h>
#define MAX 20
#define MIN 10
#define _DEBUG
#define SetBit(x) (1<<x)
int main(int argc , char* argv[])
{
printf("hello,world\n");
printf("MAX = %d , MIN = %d , MAX+MIN = %d\n",MAX,MIN,MAX+MIN);
#ifdef _DEBUG
printf("SetBit(5) = %d , SetBit(6) = %d\n",SetBit(5),SetBit(6));
printf("SetBit(SetBit(2)) = %d\n",SetBit(SetBit(2)));
#endif
return 0;
}
在git里面用 cat 命令查看一下。
现在我们已经得到hello.c文件了,接下来对该文件只进行预处理命令。
gcc -E -o hello.i hello.c
现在我们来看下hello.i文件的内容。
截取hello.i中内容的一部分,我们发现,宏定义已经被替换了,开关表达语句也消失了,包含头文件命令也被替换了,这就是预编译器将源程序进行了预处理操作。
注意:预编译器是不会对语法进行报错的,仅仅起了一个替换作用。不信,你可以在源文件中随意地修改代码,都不会报错。
2.3.2.2 对hello.i文件只进行编译操作
gcc -S -o hello.s hello.i
现在已经得到了汇编文件。
由于该程序是运行在X86架构上,所以你看到的汇编程序和在ARM架构中的语法,寄存器有很大的不同。如果想了解X86和ARM架构的区别,可以去看下我之前写的一篇文章。
2.3.2.3 对hello.S文件只进行汇编操作
gcc -c -o hello.o hello.s
我们再看下hello.o文件里面的内容
这里面的内容你当然看不懂了,看的懂你就是神人。.o文件里面的内容是机器码,显示出来就会出现稀奇古怪的内容,不用在意。
2.3.2.4 对hello.o文件只进行链接操作
gcc -o hello hello.o
执行完这个命令之后就出现exe文件了。
2.3.2.5 执行hello.exe文件
看看,结果是不是和自己预想的一样。
2.3.3 改善
上面一连串命令比较麻烦,gcc会对.c文件默认进行预处理操作,使用-c再来指明了编译、汇编,从而得到.o文件,
再将.o文件进行链接,得到可执行应用程序。
简化如下:
gcc -c -o hello.o hello.c
gcc -o hello hello.o
注意:工程(包含很多个文件)在处理的过程中都是以源文件为单位。
这种编译方式有个非常好的一点就是,当你有个文件修改了,你只需要对那个文件进行编译即可,而不需要对整个文件进行编译。
3.后缀名决定编译过程
- 总结
- 输入文件的后缀名和选项共同决定gcc到底执行那些操作
- 在编译过程中,最后的步骤都是链接
- 除非使用了-E、-S、-c选项
- 或者编译出错阻止了完整的编译过程
3.1 指定头文件目录
头文件在哪里?
-
系统目录
-
系统目录在哪?
工具链里的某个include目录。
我们下载一个工具链的时候,下载的不仅仅是可执行文件,还有很多的库文件和头文件。
这就是mingw工具链目录,可以看到在这个目录里面有系统include文件,系统库文件,可执行文件等等。
当然,我们也可以不使用库文件,编译时指定参数
-nostdinc
。
-
-
可以自己指定头文件目录
-I <头文件目录>
3.2 指定库文件
库文件在哪里?
- 系统目录
-
系统目录在哪?工具链里的某个lib目录
-
当然,我们也可以不使用库文件,编译时指定参数
-nostdlib
-
- 可以自己指定库文件目录
-L <库文件目录>
- 指定库文件
-l <abc> // 链接 libabc.so 或 lib.a
3.3 补充
在Linux中 ,.o是目标文件,相当于windows中的.obj文件 .so 为共享库,是shared object,用于动态连接的,相当于windows下的dll .a为静态库,是好多个.o合在一起,用于静态连接 。