计算机原理系列之三 ——– 如何编译目标文件

上篇文章我们讲过,我们写出来的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

五、总结

build

  1. C文件的编译过程需要经过预处理汇编编译链接,在进行编译之前生成的中间文件都是文本文件,经过编译之后生成了一个可重定位文件,然后链接之后生成了一个可执行文件
  2. 可重定位文件可执行文件都是ELF格式的文件。
  1. 所谓的文本文件,就是以ASCII字符方式存储的文件。举个例子,如果我们新建一个文件,并输入一串字符hello,那么,这个文件在计算机中保存形式是将hello根据ASCII表转化成二进制,即0110 10000110 01010110 11000110 11000110 1111,然后将这串二进制数字保存成一个文件,放在了对应的位置。

  2. 所谓的二进制文件,就是这个文件里保存的是一系列的二进制数字,这些数字人们无法直接理解其意义,但是计算机是可以以一定的规则去理解它。例如,可执行文件是一个二进制文件,当我们使用某些工具查看其内部保存的数据是,只能看到一些二进制数字,但是对应的计算机却能够执行它,并生成相应的结果。

发表评论

电子邮件地址不会被公开。 必填项已用*标注