3.2 程序编码
假设我们写一个C程序,有两个文件p1.c和p2.c,我们用unix命令行来编译这段代码
gcc -o2 -o p p1.c p2.c
o2:告诉编译器会使用第二级优化,会使得最终程序运行得更快,但是编译时间会变长,对代码进行调试会变得更加困难
这个命令实际上调用了一系列的程序,将源代码转化为可执行的代码
- C预处理器会扩展源代码,插入所有用#include命令指定的文件,并扩展所有的宏。
- 编译器会产生两个源代码的汇编代码,名字分别为p1.s和p2.s
- 汇编器会将汇编代码转化为二进制目标代码文件p1.o和p2.o
- 最后,链接器会将两个目标文件与时间标准unix库函数的代码合并。并产生最终的可执行文件
机器级代码
前面我们就说过,计算机系统使用了多种不同的抽象,利用更简单的抽象模型来隐藏实现的细节。对于机器级编程来说,有两种抽象特别重要:
①、第一种是将机器级程序的格式和行为定义为指令集体系结构(Instruction set architecture ,ISA),它定义了处理器状态、指令的格式,以及每条指令对状态的影响。大多数 ISA,包括 Intel IA32 和 x86-64,将程序的行为描述成好像每条指令是按顺序执行的,即一条指令结束后,下一条指令开始。处理器的硬件远比描述的精细复杂,它们并发的执行许多指令,但是可以采取措施保证整体行为与 ISA 指定的顺序执行完全一致。
②、第二种是机器程序使用的存储器地址是虚拟地址,提供的存储器模型看上去是一个非常大的字节数组。存储器系统的实际实现是将多个硬件存储器和操作系统软件组合起来