计算机中的信息=位+上下文。进一步,信息可以分为两大类:一类是数据,一类是指令。指令用来表明操作的动作,数据用来表明被操作的对象,这两者同时也构成了一个完整的程序。
对于数据而言,我们先来谈整数的表示和运算。无符号编码表示无符号数,补码表示有符号数,相信大家对这两种编码应该是非常熟悉的(无符号编码没有符号位,补码的最高位表示符号位)。
在整数的运算方面一共有两种,一种是算术运算,一种是逻辑运算。算术运算就指平常的加减乘数,当然要注意溢出、符号位等各种情况(这个讲起来的话太多了,而且我也担心自己讲不好,不懂的读者可以去翻翻书)。逻辑运算主要包括移位操作、或与非等,对于移位操作,一定要注意是算术右移还是逻辑右移,算法右移在所缺的位置要补齐符号位,逻辑右移要补齐零。
由于浮点型的表示和运算,我也掌握的不是太好,读者自己翻书吧。
对于指令而言,有传送指令、算术指令、逻辑指令、跳转指令以及支持过程调用和返回的指令。这些指令与c语言中的结构都是对应的,下面我会为大家讲解。
传送指令主要是mov a, b这种形式,像c语言中的赋值操作以及取某个变量的值等最后都会转化为这种指令。传送指令一共有三种寻址方式。第一种方式为立即数寻址,形式为mov $imm, EM,表示将imm传送到寄存器或者存储器EM中;第二种方式为寄存器寻址,形式为mov Ea, EM,表示将寄存器Ea中的值传送到EM中;第三种为存储器寻址,形式为mov (Ea), EM,表示将寄存器中的值所表示的存储器位置的值传送到EM中,当然这个有很多变形,但本质是一样的。要注意到不能从存储器传送到存储器。
在算术和逻辑指令中,像加减乘除、或与非、移位操作自不必说,在c语言中的这些操作与汇编中基本是没什么区别。我主要来说一下lea,即加载有效地址指令,它是mov指令的变形。指令形式为lea S, D,效果是将S的地址传送到D中,其中D必须为寄存器,像c语言中的求地址操作&就对应这种汇编语言。例如lea (Ea), Eb,在mov指令中表示将寄存器中的值所表示的存储器位置的值传送到Eb中,而在lea指令中表示将寄存器Ea中的值传送到寄存器Eb中。同时还可以用这个指令来进行一些算术运算。
跳转指令主要包括无条件跳转和有条件跳转,无条件跳转的形式为jmp Label或者jmp *Operand,后者的跳转目标是从寄存器或者存储器中读出的。对于有条件跳转,只有在符合某些条件的情况下才会跳转,比如je、jne等,那么在汇编中是如何知道这些条件成立不成立呢?除了整数寄存器,CPU还包含一组单个位的条件码寄存器,比如CF(无符号溢出)、ZF(零)、SF(负数)、OF(有符号溢出),它们描述的是最近操作的属性,也有一些指令专门用来设置条件码而不修改其他寄存器,像test、cmp。跳转指令根据这些条件码的组合情况来决定是否跳转。在c语言中像if语言、for语言、while语言、switch语言最终都会翻译成跳转指令。
最后一种指令就是支持过程调用和返回的指令。其中call指令用来过程调用,ret指令用来从过程调用中返回。c语言中的过程最后会翻译为汇编中的过程调用代码。在x86系统中一共有8个整数寄存器,其中有两个寄存器支持过程的实现,分别是栈指针%esp,帧指针%ebp。当执行call Label时(Label表示被调用的标记,也可以是*Operand的形式),计算机将返回地址(调用者的下一条指令地址)入栈,再将帧指针中保存的值(调用者的帧指针)入栈,最后将栈指针中保存的值传给帧指针,下面就开始执行被调用者中的指令,当执行ret返回时就是call指令的逆序,先将帧指针中保存的值传给栈指针,再push %ebp,将返回地址更新到程序计数器中,继续调用函数的执行。