当前位置: 首页 > news >正文

【计算机组成原理】第四章:指令系统

本篇笔记课程来源:王道计算机考研 计算机组成原理

【计算机组成原理】第四章:指令系统

  • 一、指令系统基础
    • 1. 指令格式
    • 2. 扩展操作码
  • 二、寻址
    • 1. 指令寻址
    • 2. 数据寻址
  • 三、x86 指令基础
    • 1. x86 汇编语言指令说明
    • 2. 常用的 x86 汇编指令
    • 3. AT&T 汇编语言格式
  • 四、语句的机器级表示
    • 1. 选择语句
    • 2. 循环语句
  • 五、函数调用的机器级表示
    • 1. 小结
    • 2. Call 和 Ret 指令
    • 3. 访问栈帧
    • 4. 切换栈帧
    • 5. 传递参数和返回值
  • 六、CISC 和 RISC
    • 1. 基本概念
    • 2. 对比

一、指令系统基础

1. 指令格式

  • 指令:又称机器指令。是指示计算机执行某种操作的命令,是计算机运行的最小功能单位。
    • 一台计算机的所有指令的集合构成该机的指令系统,也称为指令集
    • 一台计算机只能执行自己指令系统中的指令,不能执行其他系统的指令。如:x86 架构、ARM 架构
  • 一条指令就是机器语言的一个语句,它是一组有意义的二进制代码。通常要包括操作码字段和地址码字段两部分:
    • 操作码(OP)
      • 指明指令应该执行什么性质的操作和具有何种功能
      • 操作码是识别指令、了解指令功能和区分操作数地址内容的组成和使用方法等的关键信息。如:停机中断、算术逻辑运算、程序转移等
    • 地址码(A)
      • 指明对谁进行操作,地址码可能有 0 ~ 4 个
      • nnn 位地址码的直接寻址范围是 2n2^n2n,即如果指令总长度不变,则地址码数量越多,寻址能力越差

  • 按地址码数目分类:根据地址码数目不同,可以将指令分为
    1. 零地址指令:
      • 不需要操作数,如空操作、停机、关中断等指令
      • 堆栈计算机,两个操作数隐含存放在栈顶或次栈顶,计算结果压回栈顶,如后缀表达式
    2. 一地址指令:
      • 只需要单操作数,如 +1、-1、取反、求补等。
        • 指令含义:OP(A₁)→A₁
        • 完成一条指令需要 3 次访存:取指 → 读 A1 → 写 A1
      • 需要两个操作数,但其中一个操作数隐含在某个寄存器(如隐含在 ACC)
        • 指令含义:(ACC)OP(A₁)→ACC
        • 完成一条指令需要 2 次访存:取指 → 读 A1

      A1 指某个主存地址,(A1)表示 A1 所指向的地址中的内容

    3. 二地址指令:OP | A1(目标操作数) | A2(源操作数)
      • 常用于需要两个操作数的算术运算、逻辑运算相关指令
      • 指令含义:(A₁)OP(A₂)→A₁
      • 完成一条指令需要 4 次访存:取指 → 读 A1 → 读 A2 → 写 A1
    4. 三地址指令:OP | A1 | A2 | A3(结果)
      • 常用于需要两个操作数的算术运算、逻辑运算相关指令,并将结果写回 A3
      • 指令含义:(A₁)OP(A₂)→A₃
      • 完成一条指令需要 4 次访存:取指 → 读 A1 → 读 A2 → 写 A3
    5. 四地址指令:OP | A1 | A2 | A3(结果)| A4(下一条要执行指令的地址)
      • 指令含义同三地址指令
      • 访存次数和顺序同三地址指令
      • 执行指令后,将 PC 的值修改为 A4 所指的地址

  • 指令字长
    • 表示一条指令的总长度(可能会变)
    • 机器字长:CPU 进行依次整数运算所能处理的二进制数据的位数(通常和 ALU 直接相关)
    • 存储字长:一个存储单元中的二进制代码位数(通常和 MDR 位数相同)
  • 半字长指令、单字长指令、双字长指令,是说指令字长是机器字长的多少倍
    • 指令字长会影响取指令所需时间。如:机器字长 = 存储字长 = 16 bit,则取一条双字节指令需要 2 次访存
  • 按指令长度分类
    1. 定长指令字结构:指令系统中所有指令的长度都相等
    2. 变长指令字结构:指令系统中各种指令的长度不等

  • 操作码位数反映系统中最多可以支持多少种指令
    • 对于 nnn 位操作码可支持 2n2^n2n 种指令
  • 按操作码长度分类
    1. 定长操作码:指令系统中所有指令的操作码长度都相同
      • nnn 位操作码字段的指令系统最大能够表示 2n2^n2n 条指令
      • 优点:简化计算机硬件设计,提高指令译码和识别速度
      • 缺点:指令数量增加时会占用更多固定位,留给表示操作数地址的位数受限,灵活度较低
    2. 可变长操作码:全部指令的操作码字段位数不固定,且分散地放在指令字的不同位置上
      • 最常见的可变长操作码方法是扩展操作码,使操作码的长度随地址码的减少而增加,不同地址数的指令可以具有不同长度的操作码,从而在满足需要的前提下,有效地缩短指令长度
      • 优点:在指令长度有限的前提下仍保持比较丰富的指令种类
      • 缺点:增加了指令译码和分析的难度,使控制器的设计复杂化
      • 扩展操作码指令格式:定长指令字结构 + 可变长操作码

  • 按操作类型分类
    1. 数据传送类:CPU 与主存之间的数据传送
      • LOAD:把存储器(源)中的数据放到寄存器(目的)中
      • STORE:把寄存器(源)中的数据放到存储器(目的)中
    2. 运算类:算术逻辑操作、移位操作
      • 算术操作:加减乘除、增 1、减 1、求补、浮点运算、十进制运算等
      • 逻辑操作:与或非、异或、位操作、位测试、位清除、位求反等
      • 移位操作:算术移位、逻辑移位、循环移位(带进位和不带进位)等
    3. 程序控制类:改变程序执行流
      • 转移操作:
        • 无条件转移:JMP
        • 条件转义:JZ 结果为 0、JO 结果溢出、JC 结果有进位
        • 调用 CALL、返回 RETURN
        • 陷入(TRAP)
    4. 输入输出类:进行 CPU 和 I/O 设备之间的数据传送
      • CPU 寄存器与 IO 端口(IO 接口中的寄存器)之间的数据传送

2. 扩展操作码

  • 扩展操作码指令格式:
    • 不同地址数指令使用不同长度的操作码
    • 定长指令字结构 + 可变长操作码
  • 注意两点:
    1. 不允许短码是长码的前缀,即短操作码不能与长操作码的前面部分的代码相同
    2. 各指令的操作码一定不能重复
  • 设地址长度为 nnn,上一层留出 mmm 种状态,下一层可扩展出 m×2nm \times 2^nm×2n 种状态。
    • 设指令字长固定为 16 位,地址长度为 4 位,设计一套指令系统满足:
      在这里插入图片描述
  • 通常情况下,对使用频率较高的指令,分配较短的操作码;对使用频率较低的指令,分配较长的操作码,从而尽可能减少指令译码和分析的时间。

二、寻址

1. 指令寻址

  • 每一条指令的执行都分为 “取指令”、“执行指令” 两个阶段
    • 指令寻址:确定下一条欲执行指令的存放地址
    • 不管如何寻址,每次取指令结束后,程序计数器 PC 始终指向下一条指令的地址
    • 在 x86 处理器中,程序计数器 PC(Program Counter)通常被称为 IP(指令指针,Instruction Pointer)
  • 顺序寻址
    • (PC) + n → PCnnn 表示指令字长,指令字长会因指令长度、编址方式而不同
      • 若系统采用定长指令字(设指令字长 = 存储字长 = 16 bit)结构,主存按字编址,则 PC 每次 +1
      • 指令系统同上,但主存按字节编址,则 PC 每次 +2
      • 若系统采用变长指令字结构,主存按字节编址,CPU 会先读入一个字,根据操作码判断这条指令的总字数 nnn,然后 PC +n
  • 跳跃寻址
    • 由转移指令指出
    • 如无条件转移 JMP,将 PC 中的内容更变为指令所指向的地址

2. 数据寻址

  • 数据寻址:确定本条指令的地址码指明的真实地址
  • 在每一条指令中通过寻址特征,表示用什么方式寻址。
    • 根据形式地址(A)求出操作数的真实地址,称为有效地址(EA,Effective Address)
    • 每一个形式地址都会配上寻址特征(一对一)
    • 有 10 种数据寻址方式,因此可用 4 bit 表示寻址特征:隐含寻址、立即寻址、直接寻址、间接寻址、寄存器寻址、寄存器间接寻址、偏移寻址(相对寻址、基址寻址、变址寻址)、堆栈寻址
  • 小结:
    序号寻址方式有效地址执行指令访存次数优点缺点
    1直接寻址EA = A1简单,不需专门计算操作数的地址操作数的地址不易修改,灵活性较差
    2一次间接寻址EA = (A)2可扩大寻址范围、便于编制程序指令在执行阶段要多次访存
    3寄存器寻址EA = Ri0指令执行阶段只访问寄存器;指令字短、执行速度快;支持向量 / 矩阵运算寄存器价格昂贵,计算机中寄存器个数有限
    4寄存器间接一次寻址EA = (Ri)1比一般间接寻址相比速度更快与寄存器寻址相比指令的执行阶段需要访问主存
    5隐含寻址程序指定0有利于缩短指令字长需增加存储操作数或隐含地址的硬件
    6立即寻址A 即是操作数0指令执行阶段不访问主存,指令执行时间最短A 的位数限制了立即数的范围
    7基址寻址EA = (BR) + A1可扩大寻址范围,有利于多道程序设计,可用于编制浮动程序(内存里浮动)\
    8变址寻址EA = (IX) + A1便于处理数组问题,适合编制循环程序\
    9相对寻址EA = (PC) + A1广泛用于转移指令,便于程序浮动(程序内部浮动)\
    10堆栈寻址入栈 / 出栈时 EA 的确定方式不同硬堆栈不访存,软堆栈访存 1 次硬堆栈速度快,软堆栈成本低软堆栈速度慢、硬堆栈成本高

前情提要:

  • 假设指令字长 = 机器字长 = 存储字长
  • 假设操作数为 3
  • 以一地址指令为例
  • A 表示地址(类似指针)、(A)表示数据(类似指针指向的值)
  1. 直接寻址
    在这里插入图片描述
    • 指令字中的形式地址 A 就是操作数的真实地址 EA,即 EA = A
    • 访存次数:2 次 —— 取指令 1 次、执行指令 1 次
    • 优点:简单,指令执行阶段仅访问一次主存,不需专门计算操作数的地址
    • 缺点:A 的位数决定了该指令操作数的寻址范围,操作数的地址不易修改,灵活性较差
  2. 间接寻址
    在这里插入图片描述
    • 指令的地址字段给出的形式地址不是操作数的真正地址,而是操作数有效地址所在的存储单元的地址,也就是操作数地址的地址。即 EA = (A)
    • 访存次数:至少 3 次 —— 取指令 1 次、执行指令至少 2 次
    • 优点:可扩大寻址范围(有效地址 EA 的位数大于形式地址 A 的位数)、便于编制程序(方便地完成子程序返回)
    • 缺点:指令在执行阶段要多次访存(一次间接寻址需两次访存,多次寻址需根据存储字的最高位确定几次访存)
  3. 寄存器寻址
    在这里插入图片描述
    • CPU 内部有很多寄存器,每个寄存器有自己的编号,记为 RiR_iRi
    • 在指令字中直接给出操作数所在的寄存器编号,即 EA = Ri,其操作数在 Ri 所指的寄存器内。
    • 访存次数:1 次 —— 取指令 1 次、执行指令 0 次
    • 优点:指令在执行阶段不访问主存,只访问寄存器;指令字短且执行速度快;支持向量 / 矩阵运算
    • 缺点:寄存器价格昂贵,计算机中寄存器个数有限
  4. 寄存器间接寻址
    在这里插入图片描述
    • 寄存器 Ri 中给出的不是一个操作数,而是操作数所在主存单元的地址,即 EA = (Ri)
    • 访存次数:2 次 —— 取指令 1 次、执行指令 1 次
    • 特点:比一般间接寻址相比速度更快,但指令的执行阶段需要访问主存(因为操作数在主存中)
  5. 隐含寻址
    在这里插入图片描述
    • 不是明显地给出操作数的地址,而是在指令中隐含着操作数的地址
    • 优点:有利于缩短指令字长
    • 缺点:需增加存储操作数或隐含地址的硬件
  6. 立即寻址
    • 形式地址 A 就是操作数本身,又称为立即数,一般采用补码形式。# 表示立即寻址特征,如:LOAD # 666
    • 访存次数:1 次 —— 取指令 1 次、执行指令 0 次
    • 优点:指令执行阶段不访问主存,指令执行时间最短
    • 缺点:A 的位数限制了立即数的范围。若 A 的位数为 nnn,则立即数采用补码时的表示范围是 −2n−1-2^{n-1}2n1 ~ 2n−1−12^{n-1}-12n11

以某个地址作为起点形式地址视为 “偏移量”,以下 3 种寻址方式都属于偏移寻址。

  1. 基址寻址
    在这里插入图片描述
    • 以程序的起始存放地址作为 “起点”
    • 将 CPU 中 基址寄存器(BR,Base Address Register)的内容加上指令格式中的形式地址 A,而形成操作数的有效地址,即 EA = (BR) + A
      • 程序运行前,CPU 将 BR 的值修改为该程序的起始地址(存在操作系统 PCB 中)
      • BR 是面向操作系统的,其内容由操作系统或管理程序确定。在程序运行过程中,BR 的内容不变(作为基地址),形式地址可变(作为偏移量)
    • 如果不采用专门的 BR,而是将通用寄存器作为基址寄存器使用,则根据通用寄存器总数判断地址所需位数。如:8 个寄存器用 3 bit 表示
      • 若采用通用寄存器作为基址寄存器,可由用户决定哪个寄存器作为基址寄存器,但其内容仍由操作系统确定。
    • 优点:可扩大寻址范围(寄存寄存器的位数大于形式地址 A 的位数);用户不必考虑自己的程序存于主存的哪一空间区域,因此有利于多道程序设计,以及可用于编制浮动程序(整个程序在内存里的浮动)
  2. 变址寻址
    在这里插入图片描述
    • 程序员自己决定从哪里作为 “起点”
    • 有效地址 EA 等于指令字中的形式地址 A 与 变址寄存器(IX,Index Register)的内容相加之和,即 EA = (IA) + A
      • IX 可为专用变址寄存器,也可用通用寄存器作为变址寄存器
      • 变址寄存器是面向用户的,在程序执行过程中,变址寄存器的内容可由用户改变(IX 作为偏移量),形式地址 A 不变(作为基地址),正好与基址寻址相反
    • 优点:在数组处理过程中,可设定 A 为数组的首地址,不断改变变址寄存器 IX 的内容,便可很容易形成数组中任一数据的地址,特别适合编制循环程序
    • 实际应用中往往需要多种寻址方式复合使用(可理解为复合函数):
      • 先基址后变址寻址:EA = (IX) + ((BR) + A)
  3. 相对寻址
    在这里插入图片描述
    • 以程序计数器 PC 所指地址作为 “起点”
    • 把程序计数器 PC 的内容加上指令格式中的形式地址 A 而形成操作数的有效地址,即 EA = (PC) + A,其中 A 是相对于 PC(下一条指令地址)所指地址的偏移量,可正可负,补码表示。
    • 优点:操作数的地址不是固定的,它随着 PC 值得变化而变化,并且与指令地址之间总是相差一个固定值,因此便于程序浮动(一段代码在程序内部的浮动),相对地址广泛用于转移指令

  1. 堆栈寻址
    • 堆栈是存储器(或专用寄存器组)中一块特定的按 “先进先出(FIFO)” 原则管理的存储区,该存储区中被读 / 写单元的地址是用一个特定的寄存器给出的,该寄存器称为堆栈指针(SP,Stack Pointer)
    • 操作数存放在堆栈中,隐含是用堆栈指针 SP 作为操作数地址。
    • 堆栈分为硬堆栈、软堆栈
      1. 硬堆栈:用专门的寄存器实现堆栈,不用访存,速度快,成本高
      2. 软堆栈:在主存中划分一片区域作为堆栈,速度慢,成本低
    • 记栈顶单元为 MspM_{sp}MspYYY 是寄存器
      • 出栈指令 POP、入栈指令 PUSH
      • 如果栈顶在小地址方向:
        • 出栈 PUSH ACC:(Msp) → ACC; (SP) + 1 → SP;
        • 入栈 PUSH Y: (SP) -1 → SP; (Y) → Msp;
      • 如果栈顶在大地址方向:
        • 出栈 PUSH ACC:(Msp) → ACC; (SP) - 1 → SP;
        • 入栈 PUSH Y: (SP) +1 → SP; (Y) → Msp;

三、x86 指令基础

1. x86 汇编语言指令说明

  • 已知高级语言可通过编译器(编译程序)编译成汇编语言,汇编语言通过汇编器(汇编程序)翻译为与之对等的机器语言
    • 一条高级语言代码可编译成多条汇编语言(一对多)
    • 一条汇编语言可翻译成一条机器语言(一对一)
    • 汇编语言和机器语言都属于机器级代码

前情提要:mov 指令将源操作数复制到目的操作数所指的位置

  • 记源操作数为 s\text{s}s(source),目的操作数 d\text{d}d(destination)。如:mov d,s
  • 指明内存的读写长度:
    • dword ptr:double word pointer,双字,32 bit
    • word ptr:单字,16 bit
    • byte ptr:字节,8 bit
  • 指明内存地址:
    • 使用 [] 表示一个地址,内存地址通常用十六进制表示,结尾通常会写 h,比如 [af996h]
    • 地址前通常还会指明此次要读 / 写的数据长度。比如:move byte ptr [af996h], 5
  • 在 x86 架构中,寄存器名称以 E(Extended,扩展)开头时,其长度通常为 32 位。x86 架构 CPU 的常见寄存器:
    • 通用寄存器:以 X 结尾,表未知。
      • 扩展(E = Extended = 32 bit)通用寄存器:EAX、EBX、ECX、EDX
      • 基础通用寄存器(使用低 16 bit):AX、BC、CX、DX
      • 只使用基础的高 8 位:AH、BH、CH、DH
      • 只使用基础的低 8 位:AL、BL、CL、DL
    • 变址寄存器:用于线性表、字符串的处理,以 I 结尾,表变址 Index
      • ESI(Source)、EDI(Destination)
    • 堆栈寄存器:用于实现函数调用,以 P 结尾,表指针 Pointer。
      • EBP:堆栈基指针,Extended Base Pointer
      • ESP:堆栈顶指针,Extended Stack Pointer
汇编指令功能说明源操作数寻址方式目的操作数寻址方式
mov eax, dword ptr [ebx]ebx 所指主存地址的 32bit 复制到 eax 寄存器中寄存器间接寻址寄存器寻址
mov dword ptr [ebx], eaxeax 的内容复制到 ebx 所指主存地址的 32bit寄存器寻址寄存器间接寻址
mov eax, byte ptr [ebx]ebx 所指的主存地址的 8bit 复制到 eax寄存器间接寻址寄存器寻址
mov eax, [ebx]若未指明主存读写长度,默认 32 bit,与第一条指令等价寄存器间接寻址寄存器寻址
mov [af996h], eaxeax 的内容复制到 af996h 所指的地址(未指明长度默认 32bit )寄存器寻址直接寻址
mov eax, dword ptr [ebx+8]ebx+8 所指主存地址的 32bit 复制到 eax 寄存器中相对寻址寄存器寻址
mov eax, dword ptr [af996-12h]af996-12h 所指主存地址的 32bit 复制到 eax 寄存器中直接寻址寄存器寻址

2. 常用的 x86 汇编指令

  • 单词缩写:
    • <reg>:寄存器 register
    • <mem>:内存 memory
    • <con>:常数 constant
  • 规定:
    • 目的操作数 d 不能是常量,只能是寄存器或主存单元
    • 源操作数 s 可以是常量、寄存器、主存单元
    • x86 中不允许两个操作数同时来自主存
  • 算术运算指令
    功能英文汇编指令注释
    addadd d,s计算 d + s,结果存入 d
    subtractsub d,s计算 d - s,结果存入 d
    multiplymul d,s无符号数 d * s,乘积存入 d
    imul d,s有符号数 d * s,乘积存入 d
    dividediv s无符号数除法 edx:eax/s,商存入 eax,余数存入 edx
    idiv s有符号数除法 edx:eax/s,商存入 eax,余数存入 edx
    取负数negativeneg d将 d 取负数,结果存入 d
    自增++increaseinc d将 d ++,结果存入 d
    自减–decreasedec d将 d - -,结果存入 d
    • 除法运算默认被除数已存入 edx:eax 寄存器,两个寄存器合并拓展为 64 bit 再进行除法运算(看第二章的除法器)
    • 乘法和除法的 i 可理解为 integer —— 带符号的整数
  • 逻辑运算指令
    功能英文汇编指令注释
    andand d,s将 d、s 逐位相与,结果放回d
    oror d,s将 d、s 逐位相或,结果放回d
    notnot d将 d 逐位取反,结果放回d
    异或exclusive orxor d,s将 d、s 逐位异或,结果放回d
    左移shift leftshl d,s将d逻辑左移s位,结果放回d(通常s是常量)
    右移shift rightshr d,s将d逻辑右移s位,结果放回d(通常s是常量)
  • 其他指令
    • 用于实现分支结构、循环结构的指令:cmp、test、jmp、jxxx
    • 用于实现函数调用的指令:push、pop、call、ret
    • 用于实现数据转移的指令:mov

3. AT&T 汇编语言格式

  • x86 架构由 intel 首创,在 x86 架构下汇编语言有两大主流语法体系,分别是 AT&T 格式 和 intel 格式。
  • AT&T 格式 和 intel 格式对比
    AT&T 格式Intel 格式
    常用于Unix、LinuxWindows
    目的操作数d、源操作数sop s, d
    注:源操作数在左,目的操作数在右
    op d, s
    注:源操作数在右,目的操作数在左
    寄存器的表示mov %ebx, %eax
    注:寄存器名之前必须加"%"
    mov eax, ebx
    注:直接写寄存器名即可
    立即数的表示mov $985, %eax
    注:立即数之前必须加“$”
    mov eax, 985
    注:直接写数字即可
    主存地址的表示mov %eax, (af996h)
    注:用“小括号”
    mov [af996h], eax
    注:用“中括号”
    读写长度的表示movb $5, (af996h)
    movw $5, (af996h)
    movl $5, (af996h)
    addb $4, (af996h)
    注:指令后加 b、w、l 分别表示读写长度为 byte、word、dword
    mov byte ptr [af996h], 5
    mov word ptr [af996h], 5
    mov dword ptr [af996h], 5
    add byte ptr [af996h], 4
    注:在主存地址前说明读写长度byte、word、dword
    主存地址偏移量的表示movl -8(%ebx), %eax
    注:偏移量(基址)

    movl 4(%ebx, %ecx, 32), %eax
    注:偏移量(基址,变址,比例因子)
    mov eax, [ebx - 8]
    注:[基址+偏移量]

    mov eax, [ebx + ecx*32 + 4]
    注:[基址+变址*比例因子+偏移量]

四、语句的机器级表示

1. 选择语句

  • 选择语句即分支结构,分支结构可能改变程序的执行流
  • 无条件转移指令 jmp
    • 格式:jmp <地址>
    • 作用:PC 无条件转移至 <地址>,类似 C 语言中的 goto 语句,但无法实现 if...else... 这种复杂功能
    • 地址可来自常量 jmp 996、寄存器 jmp eax、主存单元 jmp [999h]
    • 地址还可以用 “标号” 锚定,标号要有冒号,名字自己取。如:jmp NEXTjmp iFulling
      在这里插入图片描述
  • 比较指令 cmp
    • 格式:cmp a,b(compare,比较)
    • a,ba,ba,b 可能来自常数、寄存器、主存单元
    • 比较过程跳转 第二章:数据的表示和运算(下)—— 数的比较
      cmp eax,ebx		# 比较寄存器 eax 和 ebx 中的值
      jg NEXT			# 若 eax > ebx,则跳转到 NEXT:
      
  • 条件转移指令 j...
    • 条件转移指令通常要和 cmp 指令一起使用。如上
      指令英文(完整含义)注释
      je <地址>jump when equal若 a==b 则跳转
      jne <地址>jump when not equal若 a!=b 则跳转
      jg <地址>jump when greater than若 a>b 则跳转
      jge <地址>jump when greater than or equal to若 a>=b 则跳转
      jl <地址>jump when less than若 a<b 则跳转
      jle <地址>jump when less than or equal to若 a<=b 则跳转
  • if 语句转汇编
    if (a > b) {c = a;
    } else {c = b;
    }
    
    写法一:比较用大于,但是 if 和 else 的逻辑会反过来
    mov eax,7       ; 假设变量 a = 7,存入 eax
    mov ebx,6       ; 假设变量 b = 6,存入 ebx
    cmp eax,ebx     ; 比较变量 a 和 b
    jg NEXT         ; 若 a > b,转移到 NEXT:
    mov ecx,ebx     ; 假设用 ecx 存储变量 c,令c=b
    jmp END         ; 无条件转移到 END:
    NEXT:
    mov ecx,eax     ; 假设用 ecx 存储变量c,令c=a
    END:
    
    写法二:比较反着来,if 和 else 的顺序就会一致
    mov eax,7       ; 假设变量 a = 7,存入 eax
    mov ebx,6       ; 假设变量 b = 6,存入 ebx
    cmp eax,ebx     ; 比较变量 a 和 b
    jle NEXT        ; 若 a ≤ b,转移到 NEXT:
    mov ecx,eax     ; 假设用 ecx 存储变量 c,令c=a
    jmp END         ; 无条件转移到 END:
    NEXT:
    mov ecx,ebx     ; 假设用 ecx 存储变量 c,令c=b
    END:
    

2. 循环语句

  • loop 指令
    • ecx 专门作为循环的计数器,loop 指令只能用 ecx 进行循环
    • 会对 ecx 寄存器里的值自减,然后判断 ecx 的值是否等于零,如果不等于零继续循环
    • loop Looptop 等价于
      dec ecx
      cmp ecx,0
      jne Looptop
      
    • loop 指令可能会使代码更清晰简洁,理论上,能用 loop 指令实现的功能一定能用条件转移指令实现
    • loop 扩充指令 loopxx。如 loopnz、loopz
      • loopnz:当 ecx != 0 && ZF == 0 时,继续循环
      • loopz:当 ecx != 0 && ZF == 1 时,继续循环
  • 循环语句转 loop 指令
    for (int i = 500; i > 0; i--) {做某些处理;
    }	// 循环 500 轮
    
    loop 指令
    mov ecx, 500	; 用 ecx 作为循环计数器
    Looptop:		; 循环的开始
    ...
    做某些处理
    ...
    loop Looptop	; ecx--,若 ecx != 0,跳转到 Looptop
    

  • 条件转移指令实现循环需要 4 个部分构成:
    1. 循环前的初始化
    2. 是否直接跳过循环?
    3. 循环主体
    4. 是否继续循环?
  • 循环语句转条件转移指令
    // for 写法
    int result = 0;
    for(int i = 1; i <= 100;i++) {result += i;
    } // 求 1+2+3+...+100// while 写法
    int i = 1;
    int result = 0;
    while (i <= 100) {result += i;i++;
    } // 求 1+2+3+...+100
    
    条件转移指令
    # 1、循环前的初始化
    mov eax,0    ; 用 eax 保存 result,初值为0
    mov edx,1    ; 用 edx 保存 i,初始值为 1
    # 2、是否跳出循环
    cmp edx,100  ; 比较 i 和 100
    jg L2        ; 若 i > 100,转跳到 L2 执行
    L1:          ; 循环主体
    # 3、循环主体
    add eax,edx  ; 实现 result += i
    inc edx      ; inc 自增指令,实现 i++
    # 4、是否继续循环
    cmp edx,100  ; 比较 i 和 100
    jle L1       ; 若 i <= 100,转跳到 L1 执行
    L2:          ; 跳出循环主体
    

五、函数调用的机器级表示

前情提要:

  • 程序计数器 PC 通常也被称为指令指针寄存器 IP

1. 小结

  • 本节内容较多,先做一个小结,后面分点笔记

  • 栈帧内部可能包含的内容(自底向上):
    层次内容是否存在备注
    底部上一层栈帧基址一定存在用于恢复上一层函数的栈帧
    二层若干个局部变量不一定存在有些函数可能不定义局部变量
    三层未使用区域不一定存在如果其他部分刚好是 16 B 整数倍,则不会剩下 “零头”
    四层部分寄存器值不一定存在如果这些寄存器值不是运算的中间结果,则可以不保存
    五层若干个调用参数不一定存在有些函数调用不需要传参数
    顶部IP(返回地址)一定存在(发生调用时)单反调用其他函数,就必须记录返回地址
  • 函数调用的机器级表示分析框架:
    在这里插入图片描述

2. Call 和 Ret 指令

  • 操作系统在内存中开辟一片区域用作函数调用栈,执行函数时,用于存储函数的区域称为函数的栈帧(Stack Frame)
    • 函数的栈帧:一个栈帧对应一层函数,用于保存函数大括号内定义的局部变量、保存函数调用相关的信息
    • x86 系统中,默认以 4 字节 为栈的操作单位
  • 函数调用指令:call <函数名>
    • 通常用函数名作为函数起始地址的 <标号>
    • 作用:将 IP 旧值 压栈保存(保存在函数的栈帧顶部,效果相当于 push IP);设置 IP 新值,无条件转移至被调用函数的第一条指令(效果相当于 jmp <标号>
  • 函数返回指令:ret
    • 作用:从函数的栈帧顶部找到 IP 旧值,将其出栈并恢复 IP 寄存器
  • 函数调用转汇编
    int caller() {int temp1 = 125;int temp2 = 80;int sum = add(temp1, temp2);return sum;
    }int add(int x, int y) {return x + y;
    }
    
    intel 格式
    ; caller 函数实现
    caller:push ebp           ; 保存旧的ebp(栈帧基址)mov ebp, esp       ; 新的ebp = 旧的espsub esp, 24        ; 分配24字节栈空间(局部变量等)mov dword [ebp-12], 125  ; [ebp-12] = 125 (对应C代码 temp1=125)mov dword [ebp-8], 80   ; [ebp-8]  = 80   (对应C代码 temp2=80)mov eax, [ebp-8]   ; eax = [ebp-8](temp2的值)mov [esp+4], eax   ; esp+4 = temp2(准备add的第二个参数)mov eax, [ebp-12]  ; eax = [ebp-12](temp1的值)mov [esp], eax     ; esp = temp1(准备add的第一个参数)call add           ; 调用add函数,返回值存在eaxmov [ebp-4], eax   ; 保存add返回值到[ebp-4]mov eax, [ebp-4]   ; 准备返回值(eax作为返回寄存器)leave              ; 等价于 mov esp, ebp / pop ebpret                ; 返回caller的调用者; add 函数实现
    add:push ebp           ; 保存旧的ebpmov ebp, esp       ; 新的ebp = 旧的espmov eax, [ebp+12]  ; eax = [ebp+12](第一个参数,temp1)mov edx, [ebp+8]   ; edx = [ebp+8] (第二个参数,temp2)add eax, edx       ; eax = temp1 + temp2(计算结果)leave              ; 恢复栈帧ret                ; 返回caller,eax携带返回值
    

3. 访问栈帧

  • 高地址为栈底,低地址为栈顶。因此计组中通常栈底在上,栈顶在下(与数据结构区别开来)
    在这里插入图片描述
  • 标记栈帧范围寄存器:
    • ebp:堆栈基地址,Base Pointer。指向当前栈帧的 “底部”
    • esp:堆栈顶地址,Stack Pointer。指向当前栈帧的 “顶部”
      在这里插入图片描述

  1. 访问栈帧数据方式一:pushpop 指令
    • push 🐂:入栈
      • 🐂 可以是立即数、寄存器、主存地址
      • 已知 x86 默认以 4 字节为单位,先让 esp - 4,再将 🐂 压入栈顶
    • pop 🐎:出栈
      • 🐎 可以是寄存器、主存地址
      • 栈顶元素出栈,写入 🐎,再让 esp + 4
    • push eax       		; 将寄存器 eax 的值压栈
      push 985       		; 将立即数 985 压栈
      push dword [ebp+8]  ; 将主存地址 [ebp+8] 里的数据压栈pop eax         	; 栈顶元素出栈,写入寄存器 eax
      pop dword [ebp+8]   ; 栈顶元素出栈,写入主存地址 [ebp+8]
      
  2. 访问栈帧数据方式二:mov 指令结合 esp、ebp 指针
    • mov 指令 + sub 减指令、add 加指令修改栈顶指针 esp 的值
      在这里插入图片描述
    • sub esp, 12        		; 栈顶指针 -12
      mov [esp+8], eax   		; 将 eax 的值复制到主存 [esp+8]
      mov dword [esp+4], 985  ; 将 985 复制到主存 [esp+4]
      mov eax, [ebp+8]   		; 将主存 [ebp+8] 的值复制到 eax
      mov [esp], eax     		; 将 eax 的值复制到主存 [esp]
      add esp, 8         		; 栈顶指针 +8
      

4. 切换栈帧

  • 当发生函数调用时,需要修改 ebp 和 esp,让他们指向新的函数栈帧。当函数执行完毕后,需要让 ebp 和 esp 重新指回上一层函数栈帧
  • 已知执行 call 指令后,IP 旧值压入栈顶,IP 新值指向被调用函数的第一条指令
  • 函数被调用时,每个函数开头例行执行指令
    ; 将栈帧切换为被调用函数栈帧
    push ebp		; 保存上一层函数的栈帧基址(ebp 旧值)
    mov ebp,esp		; 设置当前函数的栈帧基址(ebp 新值)
    
    • 由上面代码可知:每个函数栈帧底部一定存放上一层函数的栈帧基址(利于函数结束时返回)
    • 以上两行代码等价于 enter 指令(零地址指令)
  • 函数返回时,每个函数结尾例行执行
    ; 将栈帧切换为上一个函数栈帧
    mov esp,ebp		; 让 esp 指向当前栈帧的底部
    pop ebp			; 将 esp 所指元素出栈,写入寄存器 ebp
    
    • 已知 pop 指令会让出栈元素写入寄存器,且 esp + 4,此计可谓是一箭双雕
    • 以上两行代码等价于 leave 指令(零地址指令)
  • ret 指令:
    • 从函数的栈帧顶部(执行 call 时压入的)找到 IP 旧值,将其出栈并恢复 IP 寄存器(即让程序执行流回到 call 指令后面)

5. 传递参数和返回值

已知:

  • 栈帧最底部一定是上一层栈帧基址(ebp 旧值)
  • 栈帧最顶部一定是返回地址(当前函数的栈帧除外)
  • gcc 编译器将每个栈帧大小设置为 16 B 的整数倍(当前函数的栈帧除外),因此栈帧内可能出现空闲未使用的区域
  • 通常将局部变量集中存储在栈帧底部区域
    • C 语言中越靠前定义的局部变量越靠近栈顶,
    • [ebp - 4][ebp - 8] 等通常是访问本函数局部变量
  • 通常将调用参数集中存储再栈帧顶部区域
    • C 语言中越靠前的参数越靠近栈顶。
    • [ebp + 8][ebp + 12] 等通常是访问上一层函数传过来的参数
    • [ebp + 4] 是上一层函数的返回地址(IP 旧值)
  • 返回值
    • ret 指令前,将函数返回值写入 eax 寄存器
    • eax 是通用寄存器,存在数据丢失风险,因此可以把部分寄存器值保存在栈帧中防止丢失
  • 依旧是这块代码:
    int caller() {int temp1 = 125;int temp2 = 80;int sum = add(temp1, temp2);return sum;
    }int add(int x, int y) {return x + y;
    }
    
    在这里插入图片描述

六、CISC 和 RISC

  • CISC 和 RISC 是指令系统的两种设计方向

1. 基本概念

  • 指令系统的 80-20 规律:典型程序中 80% 的语句仅仅使用处理机中 20% 的指令
  • CISC:复杂指令集计算机,Complex Instruction Set Computer
    • 设计思路:一条指令完成一个复杂的基本功能,一条指令可以由一个专门的电路完成
    • 代表:x86 架构
    • 主要用于:笔记本、台式机等
    • 有的复杂指令用纯硬件实现很困难,于是采用 “存储程序” 的设计思想,由一个比较通用的电路配合存储部件完成一条指令,于是就有了微指令
  • RISC:精简指令集计算机,Reduced Instruction Set Computer
    • 设计思想:一条指令完成一个基本 “动作”;多条指令组合完成一个复杂的基本功能
    • 代表:arm 架构
    • 主要用于:手机、平板等

2. 对比

对比项目CISCRISC
指令系统复杂,庞大简单,精简
指令数目一般大于200条一般小于100条
指令字长不固定定长
可访存指令不加限制(如乘法指令可访存)只有 Load / Store 指令(乘法指令不能访存)
各种指令执行时间相差较大绝大多数在一个周期内完成
各种指令使用频度相差很大(80-20 规律)都比较常用
通用寄存器数量较少
目标代码难以用优化编译生成高效的目标代码程序采用优化的编译程序,生成代码较为高效
控制方式绝大多数为微程序控制(效率更低)绝大多数为组合逻辑控制(效率更高)
指令流水线可以通过一定方式实现必须实现
http://www.dtcms.com/a/333876.html

相关文章:

  • 使用vscode插件(c cpp cmake project creator)自动生成C++程序模板
  • LeetCode 283.移动零
  • C语言:指针(5)
  • break的使用大全
  • 基于STM32单片机的智能粮仓温湿度检测蓝牙手机APP设计
  • YAML:锚点深度解析,告别重复,拥抱优雅的配置艺术
  • 初识CNN02——认识CNN2
  • 浏览器面试题及详细答案 88道(45-55)
  • MyBatis 与 MyBatis-Plus 的区别
  • 20day-人工智能-机器学习-线性回归
  • 数据处理与统计分析 —— numpy入门
  • @mcp.tool如何从函数定义映射到llm系统输入
  • Kotlin作用域函数全解:run/with/apply/let/also与this/it的魔法对决
  • LORA模块的通讯速率(915Mhz)以及通道数量规划
  • 图片滤镜处理(filters)
  • 【机器学习深度学习】生成式评测
  • 数据处理分析环境搭建+Numpy使用教程
  • Design Compiler:使用IC Compiler II Link
  • PCA降维 提升模型训练效率
  • CUDA TensorRT Python智能提示补全解决方案
  • MySQL约束知识点
  • iceberg 底层存储HDFS与juiceFS的区别
  • epoll发数据学习
  • 自己开发的VIP monitor通过TLM port口连接到RefenceModel 但是get不出transaction的问题
  • 《中国棒球知识科普》国家级运动健将标准·棒球1号位
  • 力扣(接雨水)——标准双指针
  • 最长链(二叉树直径DFS)
  • 【学习笔记】NTP服务客户端配置
  • 医疗领域名词标准化工具
  • 二分算法(模板)