1.假设一个C程序,有两个文件p1.c和p2.c。我们在一台IA32机器上,用Unix命令行编译这些代码如下:
unix> gcc -O1 -o p p1.c p2.c
实际上gcc命令调用了一系列程序,将源代码转化成可执行代码。首先,C预处理器扩展源代码,插入所有用#include命令指定的文件,并扩展所有用#define声明指定的宏。然后,编译器产生两个源代码的汇编代码,名字分别为p1.s和p2.s。接下来,汇编器将汇编代码转化成二进制目标代码文件,名为p1.o和p2.o。目标代码是机器代码的一种形式,它包括所有指令的二进制表示,但是还没有填入地址的全局至。最后,链接器将两个目标代码文件与实现库函数(例如printf)的代码合并,并产生最终的可执行代码文件p。可执行代码是我们要考虑的机器代码的第二种形式,也就是处理器执行的代码格式。
在整个编译过程中,编译器会完成大部分的工作,将把用C语言提供的相对比较抽象的执行模型表示的程序转化成处理器执行的非常基本的指令。汇编代码表示非常接近于机器代码。与机器代码的二进制格式相比,汇编代码有一个的主要特点,即它用可读性更好的文本格式来表示。能够理解汇编代码以及它与原始C代码的联系,是理解计算机如何执行程序的关键一步。
2. 操作数指示符 Eb基址寄存器 Ei变址寄存器
类型 | 格式 | 操作数值 | 名称 |
立即数 | $Imm | Imm | 立即数寻址 |
寄存器 | Ea | R[Ea] | 寄存器寻址 |
存储器 | Imm | M[Imm] | 绝对寻址 |
存取器 | (Ea) | M[R[Ea]] | 间接寻址 |
存储器 | Imm(Ea) | M[Imm+R[Ea]] | (基址+偏移量)寻址 |
存储器 | (Eb, Ei) | M[R[Eb]+R[Ei]] | 变址寻址 |
存储器 | Imm(Eb, Ei) | M[Imm+R[Eb]+R[E]i] | 变址寻址 |
存储器 | (, Ei, s) | M[R[Ei]*s] | 比例变址寻址 |
存储器 | Imm(, Ei, s) | M[Imm+R[Ei]*s] | 比例变址寻址 |
存储器 | (Eb, Ei, s) | M[R[Eb]+R[Ei]*s] | 比例变址寻址 |
存储器 | Imm(Eb, Ei, s) | M[Imm+R[Eb,]+R[Ei]*s] | 比例变址寻址 |
3.数据传送指令 MOV类中的指令将源操作数的值复制到目的操作数中。源操作数指定的值是一个立即数,寄存在寄存器中或者存储器中。目的操作数指定一个位置,或是一个寄存器,或是一个寄存器地址。IA32加了一条限制,传送指令的两个操作数不能都指向存储器位置。讲一个值从一个存储器位置复制到另一个存储器位置需要两条指令-----第一条指令将源值加载到寄存器中,第二条将该寄存器值写入目的位置。
4.算术和逻辑操作
加载有效地址 加载有效地址(load effective address)指令lead实际上是movl指令的变形。它的指令形式是从存储器读数据到寄存器,但实际上它根本就没有引用寄存器。它的第一个操作数看上去是一个存储器引用,但该指令并不是从指定的位置读入数据,而是将有效地址写入到目的操作数。这条指令可以为后面的存取器引用产生指针。
lead S, D D <---&S
另外,它还可以简洁地描述普通的算术操作。例如 lead 7(%edx, %edx, 4) %eax。如果%edx的值为x,那么%eax的值为5X+7。
5.控制 P125
汇编代码不会记录程序值的类型。相反地,不同的指令确定操作数的大小,以及他们是有符号还是无符号的。
汇编语言中,直接跳转是给出一个标号作为跳转目标的,例如“.L1”。间接跳转的写法是“*”后面跟一个操作数指示符。(间接跳转,即跳转目标是从寄存器或存储器位置中读出的)。条件跳转只能是直接跳转。在汇编代码中,跳转指令有几种不同的编码,但是最常用的都是PC(程序计数器)相关的。它们会将目标指令的地址与紧跟在跳转指令后面那条指令的地址之间的差作为编码。这些地址偏移量可以编码为1,、2或4个字节。第二种编码方式就是给出“绝对”地址,用4个字节直接指定目标。
switch 语句 switch语句可以根据一个整数索引值进行多重分支。处理具有多种可能结果的测试时,这种语句特别有用。它们不仅提高了C代码的可读性,而且通过使用跳转表这种数据结构使得实现更加高效,跳变表是一个数组,表项i是一个代码段的地址,这个代码段实现当开关索引值等于i时程序应该采取的动作。程序代码用开关索引值来执行一个跳板表内的数组引用,确定跳转指令的目标。
***C语言中的if-else语句的通用形式模板是这样的:
i