[toc]
上篇文章我们讲过,我们写出来的C文件属于文本文件,属于高级语言,而计算机只能理解特定的二进制的文件,那么怎么把文本文件翻译成计算机可以理解的二进制文件呢?
下面我们以C语言中经典的“hello, world”的编译为例来说明。为了详细观察编译过程,我们使用下面的环境来详细的研究这个过程:
- ubuntu 64位操作系统
- gcc作为编译工具
(版本信息:gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.10))
注:后续如未特殊说明,本系列所有文章都使用上述环境作为实验环境。
首先创建C文件名为hello.c
,其内容如下:
1 #include <stdio.h>
2
3 int main()
4 {
5 printf("hello, world\n");
6 return 0;
7 }
一、预处理
这个阶段中将代码文件中以#
开头的代码扩展开来,比如,以“#include ...”
形式包含的头文件、“#define ...”
定义的宏,"#ifdef ... #endif"
包含的代码,同时删除掉代码文件中的注释。
1.1 预处理命令
gcc -E hello.c -o hello.i
我们可以看到当前文件夹中生成了hello.i
的文件。
1.2 生成文件格式
使用file
命令查看生成的文件:
$ file hello.i
hello.i: C source, ASCII text
注意:和Windows不一样,Linux文件的后缀名并不会决定其文件的类型,后缀只是一个标志而已。如果要查看文件的类型,需要用
file
命令。
显然,hello.i
文件依然是一个C文件,是一个由ASCII字符组成的文本文件。
使用文本编辑器打开hello.i
可以看到,最下面的代码就是上述hello.c
文件中的第3行到第7行代码。这部分代码上面插入了按照上述预处理的规则处理之后stdio.h
的代码。
二、编译
2.1 编译命令
gcc -S hello.i -o hello.s
我们得到一个hello.s
文件。
2.2 生成文件格式
使用file
命令查看文件的格式:
$ file hello.s
hello.s: assembler source, ASCII text
由此可见,经过编译之后生成了一个汇编文件,而且汇编文件也是一个由ASCII字符组成的文本文件。
编译生成的汇编文件hello.s
如下:
.file "hello.c"
.section .rodata
.LC0:
.string "hello, world"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $.LC0, %edi
call puts
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.10) 5.4.0 20160609"
.section .note.GNU-stack,"",@progbits
三、汇编
3.1 汇编命令
我们使用下面的命令处理汇编文件:
gcc -c hello.s -o hello.o
然后,得到了一个名为hello.o
文件。
3.2 生成文件格式
使用file
命令查看一下hello.o
文件的格式:
$ file hello.o
hello.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
可以看出hello.o
是一个针对x86-64机器的可重定位文件(Relocatable object programs)
。
四、链接
4.1 链接命令
这个阶段将重定位文件及其所需要的库文件链接成可执行文件(Executable object program)
。
gcc hello.o -o hello
4.2 生成文件格式
使用file
命令查看一下hello
文件的格式:
$ file hello
hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=d4417a09df150c6574d63eeb99578801b07543ca, not stripped
上述信息可以知道,hello
是一个适用于x86-64机器上的可执行文件(Executable file)
。
以上是我们为了搞清楚编译过程,而把编译的各个阶段都单独列了出来,其实,将C文件编译成可执行文件,只需要以下一条命令就可以了:
gcc -o hello hello.c
五、总结
- C文件的编译过程需要经过预处理、汇编、编译和链接,在进行编译之前生成的中间文件都是文本文件,经过编译之后生成了一个可重定位文件,然后链接之后生成了一个可执行文件。
可重定位文件
和可执行文件
都是ELF格式的文件。
所谓的文本文件,就是以ASCII字符方式存储的文件。举个例子,如果我们新建一个文件,并输入一串字符
hello
,那么,这个文件在计算机中保存形式是将h
、e
、l
、l
和o
根据ASCII表
转化成二进制,即0110 1000
、0110 0101
、0110 1100
、0110 1100
和0110 1111
,然后将这串二进制数字保存成一个文件,放在了对应的位置。所谓的二进制文件,就是这个文件里保存的是一系列的二进制数字,这些数字人们无法直接理解其意义,但是计算机是可以以一定的规则去理解它。例如,可执行文件是一个二进制文件,当我们使用某些工具查看其内部保存的数据是,只能看到一些二进制数字,但是对应的计算机却能够执行它,并生成相应的结果。