目录
4.1 指令系统
4.1.1 指令集体系结构
4.1.2 指令的基本格式
4.1.3 定长操作码指令格式
4.1.4 扩展操作码指令格式
4.1.5 指令的操作类型
4.2 指令的寻址方式
4.2.1 指令寻址和数据寻址
4.2.2 常见的数据寻址方式
4.3 程序的机器级代码表示
4.3.1 常用汇编指令介绍
4.3.2 选择语句的机器级表示
4.3.3 循环语句的机器级表示
4.3.4 过程调用的机器级表示
4.4 CISC 和 RISC 的基本概念
4.4.1 复杂指令系统计算机 CISC
4.4.2 精简指令系统计算机 RISC
4.4.3 CISC 和 RISC 的比较
指令集体系结构(Instruction Set Architecture,ISA)作为计算机软硬件交互的关键枢纽,详细规定了计算机处理器所能识别与执行的全部指令及其对应功能。它涵盖操作类型(诸如加法、乘法、数据传输等)、操作数来源与去向(涉及寄存器、内存地址等)、数据类型呈现形式(像整数、浮点数等)、寻址模式(用以确定操作数地址)、可访问内存空间范畴、可用寄存器的数量及特性(包含寄存器位数、用途等),甚至囊括中断机制(用于处理突发事件)、机器状态的定义与转换以及输入 / 输出架构等多方面内容。
不同类型的计算机,从日常使用的个人电脑、大型服务器到嵌入式设备,所采用的 ISA 可能大相径庭。例如,x86 架构凭借丰富指令与强大功能,在个人电脑和服务器领域广泛应用,能够满足复杂计算与多任务处理需求;而 ARM 架构则以低功耗、高性能和灵活设计,在移动设备和嵌入式系统中占据主导,适配资源受限环境。ISA 的存在,让软件开发者得以依据特定硬件平台编写高效程序,充分发挥硬件性能优势。
一条完整指令通常由操作码字段和地址码字段构成。操作码明确指令的操作性质与功能,不同指令系统中,其编码方式与代表操作种类各异,少则几十种,多则数百种。地址码字段负责提供操作数或其地址信息,它可以是操作数本身(立即寻址,如 “ADD R1, #5” 中的 “#5”)、内存地址(计算机借此从内存读取操作数)或寄存器编号(指示操作数所在寄存器)。地址码设计与计算机寻址方式紧密相连,合理设计可提升指令执行效率与灵活性。
定长操作码指令格式是一种常见且易理解的设计。在此格式中,指令字的最高位部分被分配固定数量位来表示操作码。例如,操作码字段固定为 n 位时,依据二进制组合原理,该指令系统最多能表示 2^n 条不同指令,如 n = 6 时,可表示 2^6 = 64 种操作。
定长操作码简化了硬件设计,因操作码长度与位置固定,指令译码器能快速准确识别指令操作类型,提升译码与执行效率。在 32 位或更长字长的计算机中更为常见,可充分利用指令字空间,保证指令功能丰富的同时维持硬件简洁高效。不过,当指令系统需扩展新指令时,固定长度操作码可能无法满足指令种类增长需求。
为在有限指令字长下保持丰富指令种类,扩展操作码指令格式应运而生。此格式中,指令操作码字段位数并非固定,而是依指令复杂程度和地址码数量动态调整,且操作码字段分散于指令字不同位置。
通常,扩展操作码使操作码长度随地址码数量减少而增加。简单常用指令,因操作数隐含或地址信息需求少,可分配较短操作码以提高执行效率;复杂指令因需更多地址信息指定操作数,则分配较长操作码。例如,三地址指令操作码可能较短,零地址指令操作码可能较长。
设计扩展操作码指令格式需遵循两条原则:一是短码不能是长码前缀,避免指令译码歧义;二是各指令操作码必须唯一。合理运用扩展操作码,可在满足指令功能前提下缩短指令字长、提高编码效率,但也增加了指令译码与分析难度,对计算机控制器设计要求更高。
计算机指令系统中的指令按功能可大致分为以下几类:
寻址方式是指令系统的重要概念,分为指令寻址和数据寻址。
指令寻址用于确定下一条待执行指令在内存中的地址,主要有顺序寻址和跳跃寻址两种方式。顺序寻址通过程序计数器(PC)自动确定下一条指令地址,每条指令执行完毕,PC 值自动增加一个指令字长,指向下一条指令内存地址。例如,在指令字长为 4 字节的系统中,当前指令地址为 0x1000,执行后 PC 值变为 0x1004,指向内存地址为 0x1004 的下一条指令,适用于顺序结构程序段。
跳跃寻址通过转移类指令实现,执行转移指令时,根据指令指定条件和目标地址改变 PC 值,使程序跳转到目标地址执行。如条件转移指令 “BEQ R1, R2, label”,当寄存器 R1 和 R2 值相等时,程序跳转到标签 label 对应的地址执行,实现程序条件控制和循环等复杂结构。
数据寻址则是确定指令中操作数地址的方式。计算机执行指令时需获取操作数进行运算或处理,不同数据寻址方式为程序员提供灵活多样的操作数访问手段,满足复杂编程需求。
寻址方式 |
地址码含义 |
操作数获取方式 |
优点 |
缺点 |
应用场景 |
直接寻址 |
操作数在内存中的真实地址 |
直接根据地址码从内存读取 |
寻址简单,硬件易实现,快速定位操作数 |
地址码固定,缺乏灵活性,操作数地址变需改指令 |
操作数地址固定且不常变化场景 |
间接寻址 |
操作数地址的地址 |
先根据地址码从内存读地址,再据此地址读操作数 |
寻址灵活,可动态改变操作数地址 |
额外内存访问开销,降低指令执行速度 |
链表、数组动态访问,依条件改变操作数地址 |
立即寻址 |
操作数本身 |
指令中直接获取操作数 |
执行时无需额外内存访问,速度快 |
操作数含在指令中使指令变长,受地址码字段长度限,常数范围有限 |
寄存器初始化,使用固定常数运算 |
变址寻址 |
变址寄存器内容 + 位移值得到操作数地址 |
计算地址后从内存读取 |
适合数组、字符串处理,改变变址寄存器值可方便访问不同元素 |
需设置变址寄存器 |
数组、字符串等数据结构操作 |
相对寻址 |
程序计数器当前值 + 位移值得到操作数地址 |
计算地址后获取操作数 |
程序在内存位置可变,无需修改指令地址部分 |
仅适用于与 PC 相关的地址计算 |
程序循环结构、子程序调用返回地址计算 |
基址寻址 |
基址寄存器内容 + 形式地址得到操作数有效地址 |
计算地址后从内存读取 |
解决程序动态定位,用于查表、数组操作、功能部件寄存器访问 |
需设置基址寄存器 |
多道程序环境下程序动态定位,特定数据结构操作 |
堆栈寻址 |
通过堆栈指针隐含确定操作数地址 |
按堆栈规则从堆栈存取操作数 |
适合子程序调用、中断处理等需保存和恢复现场场景 |
堆栈操作有特定规则,需注意堆栈溢出 |
子程序调用、中断处理 |
寄存器寻址 |
寄存器编号 |
从寄存器获取操作数 |
运算速度快 |
寄存器数量有限 |
频繁数据处理且数据在寄存器中场景 |
寄存器间接寻址 |
寄存器内容为操作数地址 |
先读寄存器得地址,再据此读操作数 |
适合复杂数据结构访问 |
需两次访问,速度相对慢 |
复杂数据结构(如链表)访问 |
汇编语言是面向机器的低级程序设计语言,使用助记符表示机器指令,与机器语言二进制代码一一对应,便于程序员编写与硬件密切相关的程序。以下是常见汇编指令及其功能:
在高级语言中,选择语句(如 if - else 语句)用于根据条件决定程序执行路径。在机器级代码中,选择语句通过条件转移指令和比较指令实现。
以简单的 if - else 语句为例,高级语言代码可能如下:
if (a > b) {
c = a;
} else {
c = b;
}
在汇编语言中,可能的实现如下:
CMP a, b ; 比较a和b
JG L1 ; 如果a大于b,跳转到L1
MOV c, b ; 否则,将b赋值给c
JMP L2 ; 跳转到L2结束选择
L1: MOV c, a ; 将a赋值给c
L2: ; 后续代码
这里,CMP 指令比较 a 和 b 的值,JG(大于则跳转)指令根据比较结果决定是否跳转到 L1 处执行将 a 赋值给 c 的操作,若不跳转则执行将 b 赋值给 c 的操作,最后通过 JMP 指令跳转到结束选择的位置继续执行后续代码。
循环语句(如 for、while 循环)在高级语言中用于重复执行一段代码。在机器级代码中,循环通常通过条件转移指令和计数器实现。
以 while 循环为例,高级语言代码可能为:
int i = 0;
while (i < 10) {
// 循环体代码
i++;
}
在汇编语言中,可能的实现如下:
MOV i, 0 ; 初始化i为0
L1: CMP i, 10 ; 比较i和10
GE L2 ; 如果i大于等于10,跳转到L2结束循环
; 循环体代码
INC i ; i自增1
JMP L1 ; 跳转到L1继续循环
L2: ; 循环结束后的代码
这里,MOV 指令初始化循环变量 i,CMP 指令比较 i 和 10,GE(大于等于则跳转)指令根据比较结果决定是否跳转到 L2 结束循环,若不跳转则执行循环体代码,INC 指令使 i 自增 1,JMP 指令跳转到 L1 继续下一次循环。
过程调用(函数调用)在高级语言中用于实现程序模块化。在机器级代码中,过程调用涉及保存返回地址、传递参数、跳转到子程序执行以及返回原程序等操作。
当调用一个过程时,首先会将返回地址(即调用指令的下一条指令地址)压入堆栈保存,然后将参数传递给子程序(可以通过寄存器或堆栈传递)。接着,通过跳转指令(如 CALL 指令)跳转到子程序的入口地址开始执行。
在子程序执行完毕后,通过 RET 指令从堆栈中弹出返回地址,将程序执行流程返回到原程序调用点继续执行。
例如,在汇编语言中,调用一个名为 subroutine 的子程序可能如下:
PUSH EIP ; 将返回地址压入堆栈,EIP为程序计数器
MOV AX, param1 ; 将参数param1传递给子程序,这里通过寄存器传递
MOV BX, param2 ; 将参数param2传递给子程序
CALL subroutine ; 调用子程序
; 子程序执行完毕后,程序会从这里继续执行
在子程序 subroutine 中,可能的代码如下:
subroutine:
; 子程序代码
POP EIP ; 从堆栈中弹出返回地址,返回原程序
这样,通过一系列的堆栈操作和跳转指令,实现了过程调用在机器级的表示。
复杂指令系统计算机(Complex Instruction Set Computer,CISC)在指令设计上倾向于设置大量功能复杂、指令字长不固定的指令。这些指令具备强大功能,能够在一条指令内完成较为复杂的操作,比如一次内存访问、多次算术运算等。例如,某些 CISC 指令可以直接完成数组元素的读取、计算和存储,无需像简单指令那样分步执行多个指令。
CISC 的指令系统丰富多样,指令格式和寻址方式灵活多变,这使得程序员能够依据不同的应用场景和编程需求,选择最合适的指令来实现复杂功能,在高级语言的编译过程中,能够生成相对紧凑、高效的机器代码。然而,由于指令复杂,CISC 计算机的硬件设计难度较大,需要配备复杂的指令译码和执行电路,这不仅增加了硬件成本,还降低了指令的执行速度,尤其是在执行一些简单指令时,也需经过复杂的硬件处理流程,导致整体效率不高。
精简指令系统计算机(Reduced Instruction Set Computer,RISC)秉持与 CISC 截然不同的设计理念。
RISC 致力于精简指令系统,以提升计算机性能与执行效率。其核心设计理念为选取使用频率高、功能简单的指令构成指令系统,摒弃复杂指令,同时采用固定长度指令格式,减少指令格式与寻址方式种类,并以硬布线逻辑控制为主,尽量避免或减少微码控制的使用。这种设计使得 CPU 结构更为简单合理,在一个时钟周期内可执行一条甚至多条指令,大大提高了运算速度。
RISC 架构的指令系统相对较小,指令格式规整、简单,常见基本寻址方式仅有 2 - 3 种。指令长度固定,便于流水线操作执行,数据处理指令通常仅对寄存器进行操作,只有加载(LOAD)和存储(STORE)指令能够访问存储器,这有效减少了内存访问次数,显著提高了指令执行效率。由于结构简单,RISC 计算机的硬件设计相对容易,研发成本较低,并且功耗也更低,在对功耗要求严苛的移动设备和嵌入式系统中应用广泛,像 ARM 架构便是 RISC 的典型代表,在手机、平板电脑等移动设备领域占据主导地位。
比较维度 |
复杂指令系统计算机(CISC) |
精简指令系统计算机(RISC) |
指令系统 |
指令数量众多,功能复杂,指令字长不固定 |
指令数量较少,功能简单,指令长度固定 |
指令格式与寻址方式 |
灵活多样,指令格式和寻址方式种类丰富 |
相对单一,指令格式和寻址方式种类有限 |
指令执行速度 |
执行复杂指令时可能只需较少指令,但因指令复杂,硬件译码和执行电路复杂,简单指令执行速度慢,整体指令平均执行速度较慢 |
单个指令功能简单,但多数指令能在一个时钟周期内完成,指令执行速度快 |
硬件设计复杂度 |
硬件设计复杂,需配备复杂指令译码和执行电路 |
硬件设计相对简单,易于实现和维护 |
软件编程难度 |
指令丰富,可根据不同应用场景选择合适指令,对程序员要求较高,软件编程相对复杂 |
指令系统精简,编程相对简单,但对编译器要求较高 |
应用场景 |
适用于对指令功能多样性和代码紧凑性要求高,对性能要求相对不敏感的通用计算机系统,如早期个人电脑和大型主机 |
适用于对性能和功耗要求高的场景,如移动设备、嵌入式系统以及要求高速运算的服务器和工作站 |
研发成本 |
因硬件复杂,研发成本高 |
硬件简单,研发成本相对较低 |