精简版 · 小杯2026-08 冻结,已停止更新(发布前修订了 4 处已知错误)。后续勘误与新增内容只在正式版。看正式版(中杯)→
Skip to content

x86汇编语言基础

考情分析

程序的机器级代码表示是 2022 年大纲新增的一条,但相关知识在此之前就已经以综合题的形式出现过了——只是当时挂在别的条目下。统考主要考查 x86 指令(Intel 格式),偶尔涉及 MIPS(会附功能说明)。

大纲定位

考纲第四章(六)「高级语言程序与机器级代码之间的对应」第 1 条:编译器、汇编器和链接器的基本概念

本篇讲的汇编语言正是这条工具链的中间产物,所以先把工具链交代清楚,再看汇编本身长什么样。第 2~4 条(选择/循环/过程调用的机器级表示)见《程序的机器级表示》。

从 C 源码到可执行文件:四个工具

汇编代码不是凭空出现的——它是编译器的输出、汇编器的输入。四步流水线,每一步的输入必须是上一步的输出,顺序不能换

图 4.16 可重定位目标文件与静态库的链接

图 4.16 可重定位目标文件与静态库的链接

工具吃什么吐什么
预处理器(cpp)源程序 .c展开宏和 #include 后的 .i
编译器(cc1)预处理后的源程序汇编语言程序 .s
汇编器(as)汇编语言程序 .s可重定位目标文件 .o(机器码)
链接器(ld)若干 .o + 库文件(libc.a 等)可执行目标文件

图里 main.c 经"翻译器(cpp、cc1、as)"变成 main.o,再和自定义静态库 mylib.a、标准库 libc.a 里取出的模块一起送进静态链接器 ld,最后产出完整链接的可执行文件。

编者注(口径):考研题里常用两级简化模型——"编译程序:高级语言 机器目标代码",把编译和汇编合起来说。gcc 的细粒度划分里编译只到 .s,汇编器才产生机器码。两种口径都有人用,判题时看题目问的是哪一层:问"输入是高级语言、输出是目标代码的是谁",答编译程序;问"谁把汇编语言翻成机器语言",答汇编程序。

三个易混点:汇编程序(assembler,一个翻译工具)≠ 汇编语言程序(用汇编语言写的源代码);解释程序逐句解释执行、不产生目标代码文件;链接器的输入已经是机器码,它只做符号解析和地址重定位,不做翻译。

工具链的更完整介绍见《计算机系统层次结构》。

x86 通用寄存器

x86 有 8 个 32 位通用寄存器:

寄存器低16位高/低8位常见用途
EAXAXAH/AL累加器,函数返回值
EBXBXBH/BL基址寄存器
ECXCXCH/CL计数器(循环/移位)
EDXDXDH/DL数据寄存器,乘除法扩展
ESISI源变址寄存器
EDIDI目的变址寄存器
EBPBP基址指针(栈帧底部)
ESPSP栈指针(栈顶)

EBP 和 ESP 有专用功能,其余寄存器用途较灵活。

图 3.2 IA-32 的定点寄存器组

图 3.2 IA-32 的定点寄存器组

图里的位刻度说明了子寄存器是怎么嵌套的:EAX 是 32 位,其低 16 位叫 AXAX 再拆成高 8 位 AH 和低 8 位 AL。所以 mov al, 5 只改 EAX 的最低字节,其余 24 位不动——反汇编里看到 8 位/16 位寄存器名时要意识到这一点。

图下方还有两组本篇不展开但要认得的寄存器:EIP(指令指针,即 PC)、EFLAGS(标志寄存器,CF/ZF/SF/OF 就在里面),以及 6 个段寄存器。

操作数标记

  • <reg>:寄存器(如 eax、ebx)
  • <mem>:内存地址(如 [eax][ebp-8]dword ptr [eax+ebx*4+8]

dword ptr 是什么:内存操作数只给了地址,没说要按几个字节访问,所以要加一个长度前缀说明宽度。

前缀访问宽度
byte ptr1 字节
word ptr2 字节
dword ptr4 字节(双字)
qword ptr8 字节

真题的反汇编里几乎行行都有它,如 cmp dword ptr [ebp-0Ch], ecx 就是"把 [ebp-0Ch] 处的 4 字节和 ecx 比较"。当另一个操作数是寄存器时宽度已经确定,前缀可省。

  • <con>:立即数/常数

两个操作数不能同时为内存

怎么读一行反汇编

真题给的不是光秃秃的汇编,而是三列格式——这三道大题的格式完全一致,先认清每列是什么:

序号   虚拟地址      机器码                          汇编
 19   004010AE   C7 84 82 00 20 42 00 0A 00 00 00   mov [ecx+edx*4+00422000h], 0Ah
 20   004010B9   8B 45 F8                           mov eax, [ebp-8]
是什么能拿来算什么
序号题目给的行号定位用
虚拟地址该指令首字节的地址相对寻址的基准
机器码该指令的字节序列数它有几个字节 = 指令长度
汇编助记符 + 操作数语义

机器码那一列是最容易被浪费的信息,两条规则从它推出来:

下一条指令地址=本条地址+本条机器码字节数函数代码长度=末指令地址首指令地址+末指令长度

验证一下上面的例子:第 19 条机器码 C7 84 82 00 20 42 00 0A 00 00 00 共 11 字节,004010AE+11=004010B9 ✓ 正是第 20 条的地址。

编者注(易错):第二条公式末尾那个"+ 末指令长度"常被漏成 +1 或干脆不加。末指令通常是 ret(机器码 C31 字节),这时才恰好等于 +1。若末指令不是 ret,就要按它自己的字节数加。

常用指令

以下使用 Intel 格式(目的操作数在前,源操作数在后)。

数据传送指令

指令语法功能
movmov dst, src将 src 复制到 dst
pushpush srcESP-4,再将 src 压入栈顶
poppop dst弹出栈顶到 dst,ESP+4
lealea dst, [addr]将有效地址(非内存内容)加载到 dst

为什么 push 是 ESP 4:x86 的栈向低地址增长。栈底在高地址,压栈时栈顶指针往下走。

高地址  ┌──────────────┐
        │   已入栈数据   │
        ├──────────────┤
        │  ← 压栈前 ESP  │
        ├──────────────┤
        │  新压入的 4B   │  push:ESP 先 −4,再写入
        ├──────────────┤
        │  ← 压栈后 ESP  │
低地址  └──────────────┘

弹栈相反:先从 ESP 处读出,再 ESP +4call 压入返回地址、ret 弹出返回地址,同样各让 ESP 变化 4。统计栈深度或算某个局部变量地址时,这几个 ±4 一个都不能漏。

asm
mov eax, ebx           ; R[eax] <- R[ebx]
mov eax, [ebp+8]       ; R[eax] <- M[R[ebp]+8]
mov [ebp-4], eax       ; M[R[ebp]-4] <- R[eax]
push eax               ; ESP-4, M[ESP] <- R[eax]
pop ebx                ; R[ebx] <- M[ESP], ESP+4
lea eax, [edx+eax*2+8] ; R[eax] <- R[edx]+R[eax]*2+8(计算地址,不访存)

lea 是"取地址"指令,常被编译器用来做加法运算(如 lea eax, [edx+eax] 等价于 add eax, edx)。

算术与逻辑运算指令

指令功能示例
add加法,结果存入第一操作数add eax, 10
sub减法sub eax, ebx
inc/dec自增/自减 1inc eax
imul有符号乘法(两种形式,见下)imul eax, ebx
idiv有符号除法,被除数为 edx:eaxidiv ecx
neg取负(补码)neg eax

edx:eax 是什么:两个 32 位寄存器拼成一个 64 位数,EDX 存高 32 位、EAX 存低 32 位。乘除法都要用到它。

imul 的两种形式——真题考过这个区别:

形式写法结果去哪
单操作数imul ebx完整 64 位积落在 edx:eax
双/三操作数imul eax, ebx只保留低 32 位存回目的寄存器,高半截丢弃

道理很简单:n× n 位最多产生 2n 位,硬件永远算满 64 位,只是双操作数形式把高 32 位扔了。扔掉的部分是否有效信息,由 OF/CF 报告——当高 32 位不是低 32 位符号位的符号扩展时(即结果放不进 32 位),OF 和 CF 被置 1。

除法反过来:idiv ecxedx:eax 这个 64 位数作被除数,商放 EAX、余数放 EDX。所以除法前常见 cdq 指令——把 EAX 的符号位扩展填满 EDX。 | and/or/xor | 按位与/或/异或 | and eax, 0FH | | not | 按位取反 | not eax | | shl/shr | 逻辑左移/右移 | shl eax, 1 | | sal/sar | 算术左移/右移 | sar eax, 2 |

编者注(易错):编译器常用 shl eax, 1 代替乘 2、sar eax, 2 代替除以 4,但"移位 = 乘除 2 的幂"只对整数(补码/无符号)成立。IEEE 754 浮点数是"符号位 + 阶码 + 尾数"三段结构,整体移位会破坏这个结构——浮点乘 2 要改的是阶码字段,不是把整个位串左移。看到题目在浮点数上做移位,先怀疑它是不是在设陷阱。

asm
xor edx, edx      ; 将 edx 清零(常见技巧)
imul esi, edi, 25  ; R[esi] <- R[edi] * 25
idiv ebx           ; eax <- edx:eax / ebx(商),edx <- 余数

控制流指令

指令功能
jmp label无条件转移到 label
cmp a, babZF/SF/CF/OF 四位,不保存结果
test a, ba AND b 置 ZF/SF(CF、OF 清零),不保存结果
call label将返回地址压栈(ESP 4),转移到 label
ret弹出返回地址(ESP +4),转移回调用点

条件转移的标志条件(真题会直接问"jle 的转移条件是什么"):

助记符含义标志条件
je / jz相等ZF=1
jne / jnz不等ZF=0
jl小于(有符号)SFOF
jle小于等于(有符号)ZF=1SFOF
jg大于(有符号)ZF=0SF=OF
jge大于等于(有符号)SF=OF
jb低于(无符号)CF=1
jbe低于等于(无符号)CF=1ZF=1
ja高于(无符号)CF=0ZF=0
jae高于等于(无符号)CF=0

编者注(速记)有符号看 SFOF,无符号看 CF。有符号的判据是"SFOF 是否一致"——因为溢出会把结果的符号位翻转,SFOF 恰好说明"真实结果为负"。带"等于"的再或上一个 ZF=1

x86 减法的 CF借位(无符号 a<bCF=1),不是加法器的进位输出 Cout,两者是取反关系。推导见《ALU 与运算电路》。

asm
cmp eax, ebx
jle done        ; 若 eax <= ebx,转移到 done
test eax, eax
jz xxxx         ; 若 eax == 0,转移到 xxxx

AT&T 格式 vs Intel 格式

区别AT&TIntel
操作数顺序源, 目的目的, 源
寄存器%eaxeax
立即数$100100
内存引用(%eax)[eax]
长度后缀movl(32位) / movw(16位) / movb(8位)dword ptr / word ptr / byte ptr
复杂寻址8(%edx, %eax, 2)[edx+eax*2+8]

统考通常使用 Intel 格式。

对比示例

AT&TIntel含义
movl $100, %eaxmov eax, 100100R[eax]
movl %eax, (%ebx)mov [ebx], eaxR[eax]M[R[ebx]]
movl %eax, -8(%ebp)mov [ebp-8], eaxR[eax]M[R[ebp]-8]

考点清单

  • x86 有 8 个 32 位通用寄存器,EBP 为栈帧基址,ESP 为栈顶指针
  • 两个操作数不能同时为内存
  • lea 是取有效地址,不访存,常被用作加法/乘法
  • call = push 返回地址 + jmp 目标;ret = pop 返回地址 + jmp
  • cmp 和 test 只设标志位不保存结果,配合 jcondition 使用
  • 有符号比较用 jg/jl/jge/jle,无符号比较用 ja/jb/jae/jbe
  • 条件转移的标志条件:有符号看 SFOF 是否一致,无符号看 CF;带"等于"再或上 ZF=1
  • edx:eax = EDX 高 32 位 + EAX 低 32 位拼成 64 位;imul 单操作数形式结果落这里,双操作数形式只留低 32 位
  • 栈向低地址增长:push / call 让 ESP 4,pop / ret 让 ESP +4
  • 机器码字节数 = 指令长度,下一条地址 = 本条地址 + 该字节数
  • 移位代替乘除 2 的幂只对整数成立,浮点要改阶码字段
  • 工具链顺序:预处理 编译(出 .s 汇编(出 .o 链接(出可执行文件),每步输入是上步输出

教材出处

  • 袁春风《计算机系统基础(第 2 版)》§3.1 程序转换概述:图 3.2(p97);§4.3 链接:图 4.16(p201)

真题练习