指令基本格式
通常包括操作码字段和地址码字段两个部分
分类
按操作数
零地址指令:
- 不需要操作数,如空操作,停机和关中断等
- 堆栈计算机,两个操作数隐含在栈顶和次栈顶,计算结果压入栈顶
一地址指令:
只要单操作数,如++,–,取反,求补等
格式:\(OP(A_1)\rarr A_1\),即取\(A_1\)中的内容操作后放回\(A_1\)
三次访存:取指、读A1、写A1
需要俩操作数,但其中一个隐含在某个寄存器中(如ACC)
格式:\((ACC)OP(A_1)\rarr ACC\)
两次访存:取指、读A1
二地址指令:
用于两个操作数的算术运算和逻辑运算等
格式:\((A_1)OP(A_2)\rarr A_1\)
四次访存:取指、读A1、读A2、写A1
三地址指令:
用于两个操作数的算术和逻辑运算等
格式:\((A_1)OP(A_2)\rarr A_3\)
四次访存:取指、读A1、读A2、写A3
四地址指令:
格式:\((A_1)OP(A_2)\rarr A_3, A_4\)是吓一跳将要执行指令的地址
四次访存:取指、读A1、读A2、写A3
也就是执行指令后,PC的值修改为A4所指地址
按指令长度
指令字长:指令的总长度
- 定长指令字结构:指令系统中所有指令长度都相等
- 变长指令字结构:指令系统中所有指令长度不等
机器字长:CPU一次整数运算所能处理的二进制数据的位数(和ALU相关)
存储字长:一个存储单元中的二进制代码位数(和MDR位数相同)
分为半字长、单字长、双字长指令,会影响取指令所需时间
按操作码长度
定长操作码:所有操作码长度都相同(若有n为则最多\(2^n\)条指令)
可变长操作码:各指令操作码长度可变
按操作类型
数据传送:如LOAD,STORE
算术逻辑操作
移位操作
转移操作:如JMP,JZ1
输入输出操作
扩展操作码
定长指令字结构+可变长操作码即为扩展操作码
设计扩展操作码时,要注意:
- 不允许短码是长码的前缀
- 各指令操作码一定不能重复
对使用频率较高的指令分配较短的操作码,较低的分配较长的
例:指令字长固定16位,地址码需要四位,需要满足
- 15条三地址指令
- 12条二地址指令
- 62条一地址指令
- 32条零地址指令
则三地址指令的指令码设为0000-1110
二地址开头4bit为1111,后四位设为0000-1011,剩余的开头11xx的留给下一位地址
所以一地址开头设为111111,后六位设为1地址指令,范围是110000-111101,剩余开头11111x的留给下一位地址
所以零地址开头设为11111111111,后五位正好0-31装32条,00000-11111
总结一下:若地址长度为n,上一层留出m种状态,下一层可以扩展出\(m\times 2^n\)种状态
寻址
指令寻址
PC始终用于指向下一条要执行的指令的地址
若对一条2字节的指令寻址,但是如果是按字节编址,一条指令需要+2才能得到下一条,或者按变长指令字,PC不知道一条指令多长,无法按指定值递增,会出现问题
看两个例子:
按字编址
按字节编址
变长指令字结构
图中相同颜色的是一条指令,可以看出每条指令占的字节数都不同

跳跃寻址
可以由转移指令JMP给出指定地址码,将PC的值修改为对应地址,实现无条件转移
数据寻址
确定本条指令的地址码指明的真实地址
数据寻址有十种寻址方式,为了指定每次寻址的寻址特征,可以在每个形式地址前添加一个四位的寻址方式位,称为寻址特征
寻址特征+形式地址才是操作数的真实地址,称为有效地址(EA)
直接寻址
指令字中的形式地址A就是操作数的真实地址EA,即EA=A

需要访存两次:取指令、执行指令
优点:执行指令仅访问一次主存,不需要专门计算操作数地址
缺点:A的位数决定指令操作数的寻址范围,操作数地址不易修改
间接寻址
指令地址字段给出的是操作数有效地址所在的存储单元的地址,即地址的地址,EA=(A)
间接寻址可以间接若干次
访存三次:取指令一次,执行指令两次
优点:可以扩大寻址范围(有效地址EA的位数大于形式地址A的位数),便于编制程序
缺点:多次访存
寄存器寻址
指令字中直接给出操作数所在寄存器编号,即\(EA=R_i\),操作数位于\(R_i\)所指的寄存器内
访存一次:取指令一次
优点:执行阶段不访问主存,只访问寄存器,指令字短且速度快
缺点:价格昂贵,寄存器个数有限
寄存器间接寻址
寄存器给出的是操作数所在主存单元地址\(EA=(R_i)\)
访存两次:取指令一次,执行指令一次
特点:比一般间接寻址快
隐含寻址
指令中隐含着操作数的地址
优点:有利于缩短指令字长
缺点:需要增加存储操作数或硬件
立即寻址
形式地址A就是操作数本身,又称为立即数,一般采用补码,地址前用#表示
访存一次:取指令
偏移寻址
基址寻址
将CPU中基址寄存器(BR)的内容加上指令格式中的形式地址A,形成操作数的有效地址,即EA=(BR)+A
有的设备中会使用若干通用寄存器代替基址寄存器的功能,需要花\(\log_2n\)个bit位指明用哪个寄存器作为基址寄存器
优点:便于程序浮动,方便多道程序并发运行
注意:基址寄存器是面向操作系统的,内容由操作系统或管理程序决定,程序执行时基址寄存器内容不变
采用通用寄存器作为基址寄存器时,用户只可以决定用哪个寄存器作为基址寄存器
变址寻址
有效地址EA等于指令字中的形式地址A与变址寄存器IX的内容相加之和,即EX=(IX)+A
同样可以用通用寄存器作为变址寄存器
注:变址寄存器是面向用户的,程序执行过程中,变址寄存器的内容可以由用户改变
例:
相对寻址
把程序计数器PC的内容加上指令格式中的形式地址A而形成操作数的有效地址,即EA=(PC)+A,A是相对于PC所指地址的偏移量,补码表示
例:
优点:代码在程序内浮动时不用更改跳转指令的地址码
堆栈寻址
指操作数放在堆栈中,隐含使用堆栈指针SP作为操作数地址
堆栈是存储器内一块特定按LIFO管理的存储区,该存储区中被读写单元的地址是用一个特定的寄存器给出的,称为堆栈指针SP
汇编语言
指令格式
以mov为例,格式为mov d,s
作用为将源操作数s复制到目的操作数d所指位置
mov eax, ebx:将寄存器ebx的值复制到eaxmov eax, 5:将立即数5复制到寄存器eaxmov eax, dword ptr [地址]:将指定内存地址所指的双字值复制到寄存器eaxmov byte ptr [地址], 5:将立即数5复制到指定内存地址所指的一字节中
其中内存的读写长度:
dword ptr:双字,32bitword ptr:单字,16bitbyte ptr:字节,8bit
x86的常用寄存器
- 以E开头的寄存器长为32bit
- 前四个寄存器存储未知数据,结尾为X,称为通用寄存器
- 下面两个寄存器 处理字符串等,结尾为I,称为变址寄存器
- EBP为堆栈基指针
- ESP为堆栈顶指针
- 前四个若只想使用16bit,可以去掉前面的E
- 前四个若只想使用8bit,对应的寄存器为AH, AL,BCD同理
常用x86汇编指令
算术运算
除法这里,s为除数,被除数被隐含寻址于edx:eax中
这里的意思是两个寄存器拼接,因为在进行除法操作时,被除数会扩展其位数到原来的两倍
逻辑运算
AT&T与Intel格式
选择语句
无条件转移指令
jmp 地址
将PC无条件的转移到对应地址,或寄存器指向的地址
jmp 标号无条件转移到对应标号指明位置,标号的格式名字:
条件转移指令
一般要和cmp a,b一起使用,先比较,若上面的比较成立则执行下面的跳转指令
循环语句
条件转移指令实现
由四个部分组成:
- 循环前初始化
- 是否直接跳过循环
- 循环主体
- 是否继续循环
loop指令实现
loop默认用ecx作循环计数器
loop对应的还有某些loopx指令,如loopnz表示当ecx!=0且ZF==0时继续循环
函数调用
call与ret
函数调用:call 函数名
函数返回:ret
通常用函数名作为函数起始地址的标号
注:Intel里PC被叫做IP
- 执行call后,会将IP内的值(call的下一条)压入栈顶
- 然后进入调用的函数,将IP新值设为被调用函数第一条指令
- 调用函数栈帧出栈,ret后将IP旧值写回
访问栈帧
在虚拟内存用户区中,用户栈的栈底位于高地址,栈顶位于低地址,所以一般表示栈是倒着画的,且x86系统中,默认以四字节为栈的操作单位
寄存器ebp指向当前栈帧的底部
寄存器esp指向当前栈帧的顶部
指令:
- push:入栈操作,先让esp减4,再将数据压入
- pop:出栈操作,栈顶元素出栈,写入指定寄存器,再让esp加4
- mov:访问栈其他位置 ,结合ebp和esp指针访问数据,可以用sub/add指令修改栈顶指针esp的值
切换栈帧
当call一个新函数时,我们进入对应栈帧:
- 首先
call指令会将IP旧值(call的下一句)压栈保存,并设置IP新值为被调用函数的第一条指令 push ebp把上一层函数的ebp旧值压入栈,这个地址就是返回地址- 然后
move ebp, esp,把ebp寄存器指向到esp的位置(新函数地址) - 接下来用mov等压入数据,执行操作
- 前两个操作可以用一个
enter指令代替 - 函数返回后,新的栈帧消失,栈顶此时就是原来的ebp旧值和IP旧值
首先执行
mov esp, ebp,将esp指向当前栈帧底部然后执行
pop ebp,将esp所指元素出栈,写入寄存器ebp,实现让ebp重新指回上一层函数栈帧基址上面两个命令等价于leave指令,即从函数栈帧顶部找到IP旧值,出栈并恢复IP寄存器
传参与返回值
一个栈帧内可能包含的内容:
- 最底部:上一层栈帧基址(ebp、旧值)
- 最顶部:返回地址(当前函数栈帧除外)
- 靠近底部:用于存储局部变量,越靠前定义的越靠近栈顶
- 靠近顶部:用于存储形式变量,参数列表中越靠前的参数越靠近栈顶
- 中间:可能有未使用区域没因为栈帧大小要求16字节整数倍
CISC和RISC
CISC:复杂指令集,一条指令完成一个复杂的基本功能,用于x86架构
如果有复杂指令用纯硬件实现困难,可以采用存储程序的设计思想,由一个比较通用的电路配合存储部件完成一条指令,即形成微程序
RISC:精简指令集,一条指令完成一个基本动作,多条指令组合完成一个复杂的基本功能 ,用于arm架构