作为这一系列文章中的最后一篇,这篇文章我打算讨论的是从编译到执行的全过程。因为许多地方都是要有了汇编的基础知识以后才方便讨论,所以我把它放到了最后一篇。
编译
编译并不是对汇编代码来说的,而是对更高级的语言,如C、C++来说的。如果一个语言最终的编译结果是可执行文件,那么它一定会先被编译为汇编语言,然后再被汇编、链接为可执行文件。对于C和C++来说,大部分的编译器都支持输出汇编结果。比如说对于test.c
, 我们想查看其编译后的汇编代码,只需要在命令行中键入
clang test.c -S -o test.s
然后就会生成一个包含其汇编代码的test.s
文件。
研究编译器生成的汇编代码很有意义。因为现代的编译器,其都针对不同的平台、架构有许多优化,这对于我们写汇编代码是很有意义的。比如说,对
return 0;
的编译结果,是
xorl %eax, %eax
retq
事实上,通过异或自身来清零这一操作,在任何架构上都是最高效的。
汇编
所谓汇编,就是输入我们的汇编代码,输出目标文件。什么是目标文件呢?假设我们有一个汇编文件test.s
, 然后我们利用
as test.s -o test.o
生成一个test.o
文件。然后,我们在终端下利用file
指令查看其文件类型:
$ file test.o
test.o: Mach-O 64-bit object x86_64
可以看到, 这个文件是object, 也就是目标文件。
那么,目标文件是做什么用的呢?要了解这个,首先我们需要知道「汇编」这一步骤究竟做了什么。
我们知道,汇编语言可以看作机器码的human-readable版本。因此,从最直观来看,汇编只需要把汇编代码翻译为机器码就ok了,也就是汇编代码直接变成可执行文件。这个粗略来看是对的,对于大多数代码来说,确实直接翻译为机器码就好了。但是,如果真的是这样,随着人们写的代码越来越多,汇编器的有一项工作的负担就越来越重——翻译符号。我们之前在汇编语言中大量运用了标签,一个标签就对应一个地址。此外,我们也可以引用别的文件、动态链接库的标签。因此,对于一个标签,其可能的情况有好多好多种。所以,人们就把这部分功能从汇编器中解放出来,同时,汇编器就变成了对于一个汇编文件,输出其目标文件。目标文件几乎包含的就是可执行文件中的机器码,但是标签部分却是空缺的。其会把所有遇到的符号放到一个符号表中,以便查阅。
举个例子,我们现在有两个汇编程序test.s
和tmp.s
, 其代码分别如下:
tmp.s
:
# tmp.s
.data
.globl tmp_var
tmp_var: .quad 0x1145