5.1 指令系统的基本概念
5.1.1 机器语言、机器指令与指令系统
机器语言是计算机唯一可以识别的语言,本质是一串二进制代码,机器语言由一条条机器指令构成。
-
机器指令:机器语言的单条语句,用于告诉计算机完成一项操作。
-
指令系统:一台计算机中全部机器指令的集合。
5.1.2 指令格式
一条指令由两大字段构成:
|--------------|-------------|
| 操作码字段 OP | 地址码字段 A |
-
操作码 OP :表征指令要完成的操作性质与功能。分为定长操作码 、变长(不定长)操作码。定长操作码的位数直接决定指令系统最多支持的指令种类。
-
地址码 A:存放操作相关地址信息,可以是立即数、寄存器编号、主存地址、I/O 设备地址。
各类地址指令
|----|----|----|----|----|
| OP | A1 | A2 | A3 | A4 |
-
四地址指令:OP A₁ A₂ A₃ A₄。A₁、A₂为源操作数地址;A₃存放结果;A₄存放下一条指令地址。
-
三地址指令:OP A₁ A₂ A₃。去掉 A₄,下一条指令地址由 PC 自增得到。
-
二地址指令:OP A₁ A₂。省略结果地址 A₃,运算结果写回 A₁;也可写回累加器 ACC;PC 自增。
-
一地址指令:OP A₁。一个源操作数隐含在 ACC 中,运算结果写回 ACC;PC 自增。例子:INC 自增指令。
-
零地址指令:只有操作码,无地址字段。
-
一类:无操作数,如空操作、停机、关中断指令。
-
一类:操作数隐含,堆栈指令 PUSH、POP,操作数由堆栈指针 SP 隐含给出。
-
5.1.3 扩展操作码技术
指令字长固定,把部分地址码的位数拿出来分配给操作码,以此实现变长操作码,在有限指令字长下扩充指令的数量。
5.1.4 指令的操作类型
-
数据传送指令
-
MOV:源操作数传送至目的位置,源操作数保持不变;
-
LOAD:主存→寄存器;STORE:寄存器→主存;
-
EXC:交换源、目的位置内容;
-
PUSH 进栈、POP 出栈。
-
-
算术运算指令:加、减、乘、除;INC (自增)、DEC (自减)。
-
逻辑运算指令:与、或、求反、异或。
-
移位指令
-
算术移位:需要处理符号位;
-
逻辑移位:不区分符号位。
-
-
转移类指令:修改 PC 的值,打破程序顺序执行。
-
无条件跳转 JUMP:分为绝对跳转(直接将目标地址送入 PC)、相对跳转(PC 加上偏移量得到目标地址)。
-
条件转移 BRANCH:根据条件是否成立决定是否跳转。
-
CALL 调用指令:将下一条指令地址压栈保存,修改 PC 为子程序入口地址。
-
RETURN 返回指令:弹出栈内保存的返回地址,回到原程序继续执行。
-
TRAP 陷阱指令:出现意外故障,跳转到故障处理程序。
-
-
I/O 指令:完成 CPU 与外设之间数据、状态、控制信息的传送。
操作数的数据类型:地址、数字、字符;文本字符串,计算机内部需要转换为二进制编码,常用 ASCII、UTF‑8。
5.2 数据对齐与大小端存放方式
计算机内存按字节编址。
5.2.1 数据对齐
理论上数据可以存放在任意内存地址;为提升内存读取速度,要求数据起始地址为自身字节大小的整数倍。
结构体对齐两条规则
-
结构体成员对齐:每个成员起始地址是自身大小的整数倍。
-
结构体整体对齐:结构体总大小必须等于内部最大成员大小的整数倍。
32 位机常见类型字节大小: char:1B;short:2B;int/float:4B;double:8B;指针:4B。
定义结构体:
cpp
struct Test{
char a; // 1B
short b; // 2B
int c; // 4B
double d; // 8B
};
设定结构体起始地址:0x00
|------------|---------|-----|-----|-----|
| 内存地址 | 字节1 | 字节2 | 字节3 | 字节4 |
| 0x00~0x03 | a(char) | 填充 | b(short) ||
| 0x04~0x07 | c(int) ||||
| 0x08~0x0B | d(double ) ||||
| 0x0C~0x0F | d(double ) ||||
总大小校验 :实际占用16B,最大成员为8B,16是8的整数倍,满足结构体整体对齐规则,最终结构体大小为 16B。
核心总结:结构体对齐会产生内存填充位,牺牲少量内存空间,换取CPU高速批量读取内存的效率。
5.2.2 大小端存储
-
大端模式 :低内存地址存放数据的高位字节,符合日常书写阅读习惯。
-
小端模式:低内存地址存放数据的低位字节。
大小端存储实例
举例:16位十六进制数据 0x1234(高位字节:0x12,低位字节:0x34),占用2字节内存,设定起始内存地址为 0x0001。
| 内存地址 | 0x0001(低地址) | 0x0002(高地址) |
|---|---|---|
| 大端模式存储内容 | 0x12(高位字节) | 0x34(低位字节) |
| 小端模式存储内容 | 0x34(低位字节) | 0x12(高位字节) |
5.3 指令寻址与数据寻址
5.3.1 指令寻址
PC 始终保存下一条待取指令的地址。
-
顺序寻址:PC 自增,程序顺序执行。
-
跳跃寻址:转移类指令修改 PC 的值,实现跳转分支。
5.3.2 数据寻址基础
指令地址码字段给出的一般不是真实操作数地址,称为形式地址 A ; 经过运算得到真正访问内存的地址叫做有效地址 EA。
|----|-------|--------|
| OP | 寻址特征位 | 形式地址 A |
-
立即寻址(立即数寻址) 地址码字段存放的就是操作数本身,属于指令的一部分,只能读不能修改;不访问主存。
-
直接寻址 形式地址 A 就是有效地址 EA;访问 1 次主存取出操作数。
-
寄存器寻址 地址码给出寄存器编号,有效地址就是寄存器编号;操作数存放在寄存器中,无需访存。
-
隐含寻址 指令中不给出操作数地址,操作数隐含在 ACC;缩短指令字长,需要额外硬件支持。
-
间接寻址 指令中的形式地址 A 是有效地址的内存地址。

-
一次间接寻址:取操作数需要2 次访存;
-
n 次间接寻址:取操作数需要 n+1 次访存(不含取指令)。
-
-
寄存器间接寻址 地址码给出寄存器编号;寄存器内存放有效地址;用有效地址访问主存得到操作数。只需要 1 次访存,相比多次间接寻址减少访存次数,扩大寻址范围。
-
基址寻址 EA = (BR) + A BR 基址寄存器存放程序段起始基地址,A 为指令内偏移量。也可以如下图指定一个通用寄存器R作为基址寄存器。

- 基址寄存器由操作系统修改,用户不可修改;面向操作系统,用于程序重定位。
-
变址寻址 EA = A + (IX) A 为指令中给出的基址,IX 变址寄存器存放偏移量。

- 变址寄存器用户可以修改;面向用户,适合数组处理、循环程序。
-
相对寻址 EA = (PC) + A A 是偏移量;有效地址随 PC 变化,和当前指令保持固定偏移。适合程序浮动,程序加载到内存任意位置都可以正常寻址,相对跳转就使用相对寻址。
-
堆栈寻址 依靠堆栈指针 SP 隐含操作数地址。
-
硬堆栈:寄存器实现;
-
软堆栈:主存空间实现。
数组处理常用变址寻址:形式地址存放数组首地址,不断修改变址寄存器(用户即可修改),依次访问数组各个元素。
相对寻址适合程序浮动原因:有效地址由 PC 加偏移得到,只关心指令之间相对距离,和程序加载的起始物理地址无关,程序重定位后依旧可以正常寻址。
5.4 CISC 与 RISC
CISC:复杂指令集,代表 X86;RISC:精简指令集,代表 ARM、MIPS。
RISC 诞生依据:程序 80% 的语句只使用 20% 简单指令,剩余功能可以通过简单指令组合实现。
|--------|------------|---------------------|
| 对比项 | CISC | RISC |
| 指令系统 | 指令数量庞大 | 指令精简 |
| 指令字长 | 长度不固定,格式多 | 指令字长固定,格式少 |
| 寻址方式 | 寻址方式多 | 寻址方式少 |
| 访存指令 | 大量访存指令 | 仅 LOAD、STORE 可以访问内存 |
| 通用寄存器 | 数量少 | 数量多 |
| 指令频度 | 指令使用频度差距大 | 指令都是常用简单指令,频度差距小 |
| 指令执行时间 | 执行时间差异大 | 大多单时钟周期完成,流水线 |
| 控制器 | 微程序控制为主 | 硬布线控制为主 |
| 编译支持 | 难以生成高效目标代码 | 便于优化编译 |
RISC 优点
-
通用寄存器多,可以减少访存次数,运算速度高;
-
指令格式规整,便于编译优化;
-
指令译码简单,译码耗时短;
-
硬布线控制器,CPU 芯片占用面积小;
-
设计简单。
5.5 高级语言与机器语言对应关系
-
低级语言:机器语言、汇编语言。
-
C 语言:编译型语言,需要经过预处理、编译、汇编、链接,生成机器语言才可运行。
-
Python:解释型语言,运行时才逐条翻译成机器代码。
GCC 编译 hello.c 完整流程
-
预处理:hello.c → hello.i;处理 #开头指令:文件包含、宏替换、条件编译。
-
编译:hello.i → hello.s;翻译为汇编文本。
-
汇编:hello.s → hello.o 可重定位目标文件,翻译成机器指令。
-
链接:hello.o 和库文件链接 → hello.exe 可执行文件。
5.6 X86 汇编基础
X86 属于 CISC 架构,来源于 8086 系列处理器,程序员可见寄存器包含:通用寄存器、指令寄存器、标志寄存器。指令格式灵活、寻址方式多,支持丰富运算与数据传送。
5.6.1 数据传送指令(重点考点)
1. MOV 数据传送指令
规则:不改变源操作数;禁止内存→内存直接传送,必须通过寄存器中转。
cpp
# 合法示例
MOV EAX, 10 # 立即数10传入寄存器EAX
MOV EBX, EAX # 寄存器→寄存器传送
MOV [0x1000], EAX # 寄存器→主存
# 非法示例
MOV [0x1000], [0x2000] # 错误!不允许内存直接传内存
2. PUSH / POP 堆栈指令
PUSH:数据压入栈;POP:数据弹出栈。
规则:PUSH 可操作寄存器、内存、立即数;POP 不能使用立即数。
cpp
PUSH EAX # 将EAX数据压栈
PUSH 0x20 # 立即数压栈(合法)
POP EBX # 栈顶数据弹出到EBX
POP 0x10 # 错误!POP不支持立即数
3. LEA 加载有效地址指令
功能:取出内存单元的地址 ,而非数据;光算地址,不访问内存,存粹的算术运算,所以编译器经常用 LEA 代替乘法加法,速度比普通算数指令更快。。
规则:源操作数必须是内存,目的必须是寄存器。
cpp
LEA EAX, [EBX+4*ECX+8] # 计算地址:EBX+4*ECX+8,存入EAX
# 无内存访问,仅做算术运算,编译器常用此优化计算
5.6.2 算术与逻辑运算指令
cpp
INC EAX # 自增1
DEC EBX # 自减1
NEG EAX # 按位取反(求补)
SHL EAX,1 # 逻辑左移1位
SHR EAX,1 # 逻辑右移1位
IMUL EBX # 有符号乘法
IDIV ECX # 有符号除法
5.6.3 状态与跳转指令(必考)
CMP / TEST :只修改条件码标志位,不修改操作数本身。
CMP A,B 等价 SUB A,B(但不保存结果);TEST A,B 等价 AND A,B(但不保存结果)。
cpp
; ========== CMP 用法:比大小、比相等 ==========
CMP EAX,10 ; 执行 EAX-10,设置标志位
JE label1 ; 相等跳转
JG label2 ; 大于跳转
JL label3 ; 小于跳转
; ========== TEST 用法:判二进制位、判是否为0 ==========
TEST EAX,0x1 ; 执行 EAX & 00000001,判断最低位是否为1
JNZ label4 ; 最低位为1则跳转
TEST EAX,EAX ; 执行 EAX&EAX,判断EAX是否等于0
JZ label5 ; 结果为0(EAX=0)则跳转
无条件跳转、子程序调用
cpp
JMP label3 # 无条件跳转
CALL func1 # 保存下一条指令地址入栈,跳转至子程序
RET # 弹出栈中地址,返回原程序继续执行
-
JUMP 无条件跳转:标号汇编后换算成相对偏移量(目标地址 与 下一条指令地址的差值)。
-
JCOND 条件跳转:条件满足跳转,不满足顺序执行。
5.7 MIPS 汇编基础
MIPS 属于典型 RISC 精简指令集 ,指令规整、字长固定、仅LOAD/STORE可访存,流水线效率高。核心特性:字节编址、字对齐、默认大端存储、所有运算必须在寄存器中完成。
5.7.1 MIPS 核心硬性规则(必考)
-
运算类指令(加减与或移位)绝对不能访问内存;
-
只有 lw(读内存)、sw(写内存) 两条指令可以访存;
-
数据必须先 Load 进寄存器,运算后再 Store 写回内存。
bash
# 格式:lw 目标寄存器, 偏移量(基址寄存器)
lw $t0, 0($s0) # 将$s0指向的内存数据,加载到临时寄存器$t0
sw $t0, 4($s0) # 将$t0的数据,写入$s0+4的内存单元
5.7.2 MIPS 与 X86 核心区别
-
X86(CISC):指令长短不一、指令多、寻址多、任意指令可访存;
-
MIPS(RISC):指令定长、仅Load/Store访存、运算全在寄存器、适合流水线、编译优化性强。