这篇博客主要讲解编译的过程被隐藏的步骤,只讲解主要的内容,想要更详细的了解,还需要去看关于链接,装载的书籍才行。
我们回顾下平时在控制台用gcc编译源代码时的情景。假如有一份非常简单的C源代码,叫helloworld.c
如下:
int main
{
int a = 1;
}
使用gcc来编译,使用如下命令:
gcc helloworld.c -o hello
然后就在linux平台下生成了一个可执行文件hello。这么这中间,编译器到底做了哪些步骤呢?大致可以分为4个步骤:预处理,编译,汇编,链接。
预处理
预处理相信大家都知道,我们定义的宏,#include指令等等,都在预处理阶段被处理。具体规则是:
将所有的#define删除,并展开所有的宏定义
处理所有条件预编译指令:比如 #if,#ifdef,#elif,#else,#endif
处理#include预编译指令,将被包含的文件插入到该预编译指令的位置
删除所有注释
添加行号和文件号
保留#pragma编译器指令
我们现在来举例子说明上面的规则到底是什么意思。我们现在已经有了一个C文件叫helloworld.c就是前面提到的,现在我们引用另外一个叫fun.c的文件,这个文件也很简单,如下:
int myfun()
{
return 1;
}
注意到,我是故意空了一部分空白才写函数的,myfun在fun.c的第5行被声明。然后我们在helloworld.c里添加#include “fun.c”
#include "fun.c"
int main()
{
int a = 1;
}
好的,现在我们使用gcc -E helloworld.c -o helloworld.i命令,之后我们得到了一个heeloworld.i文件,打开这个文件可以看到:
# 1 "helloworld.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<command-line>" 2
# 1 "helloworld.c"
# 1 "fun.c" 1
int myfun()
{
return 1;
}
# 9 "helloworld.c" 2
int main()
{
int a = 1;
}
简单的分析下这个文件,# 1 “fun.c”1 这一行和# 1 “helloworld.c”这一行中间有7个空行,正好是helloworld.c文件里#include “fun.c”这句话正好是在第8行写的。而# 1 “fun.c” 1和int myfun()这行中间有4个空行,而 int myfun()也是正好定义在fun.c的第5行。另外,你也可以试试#include
编译
好了,预处理结束了,我们得到了helloworld.i文件,之后的步骤是什么呢?之后开始编译了,我们把预处理完的文件进行词法分析,语法分析,语义分析以及优化后生成了相应的汇编代码文件,我们来看一看这个过程。
使用gcc -S helloworld.c -o helloworld.s
我们查看一下helloworld.s文件
.file "helloworld.c"
.text
.globl myfun
.type myfun, @function
myfun:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $1, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size myfun, .-myfun
.globl main
.type main, @function
main:
.LFB1:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $1, -4(%rbp)
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE1:
.size main, .-main
.ident "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609"
.section .note.GNU-stack,"",@progbits
汇编
我们现在有了汇编代码,使用汇编器,可以将汇编代码转换成机器可以执行的指令,每一个汇编语句几乎都会对应一条机器指令。我们现在就来实现这一过程,值得一提的是,.s的汇编文件是文本文件,而.o的目标文件是二进制文件。
我们使用gcc -c helloworld.s -o hello1.o命令来进行汇编,就能得到helloworld.o这个目标文件了,后面的博客我还会写如何查看这个目标文件,这个目标文件里都有什么,所以现在就暂时了解这么多把。hello1是我随便起的名字,没什么特别的含义,名字可以自己随便取的。
链接
有了目标文件,比如说a.o, b.o,我们可以将它们链接成一个可以运行的程序,目前a.o和b.o还不能运行,因为a.o里引用的别的函数啊,变量之类的地址还没有被确定(一般没确定的时候就先写为0)。用连接器ld来连接。链接以后也会继续开博客讲,所以目前也就写到这里。大概就知道四个步骤,以及这四个步骤都干了什么就行了。