常用汇编指令介绍

4.1 和 4.2 讲的是”指令这个东西长什么样”,4.3 换成”一段 C 程序落到指令上是什么样”。这一节先把工具铺开——寄存器、两种汇编书写格式、常用指令——后面三节才拿这些工具去翻译 if、for 和函数调用。

统考不要求写汇编,但要求读汇编:给一段代码,判断它在干什么、访存几次、哪条指令改了哪个标志位。所以这一节的目标不是记忆助记符表,而是建立”看到一条指令就能判断它属于 4.1.5 哪一类、动没动主存、动没动标志位”的反射。

机制

x86 的寄存器:名字的三层沿革

64 位32 位16 位8 位惯用途
RAXEAXAXAL/AH累加器、返回值
RBXEBXBXBL/BH基址
RCXECXCXCL/CH计数
RDXEDXDXDL/DH数据、乘除法高位
RSIESISISIL源变址
RDIEDIDIDIL目的变址
RBPEBPBPBPL栈帧基址
RSPESPSPSPL栈顶指针
R8~R15R8D~R8W~R8B~x86-64 新增

E 是 Extended(16→32),R 是 Register(32→64)。写 EAX 就是 RAX 的低 32 位——它们不是两个寄存器,是同一个寄存器的两种宽度视图。这一条在读真题的位数陷阱时用得上。

“惯用途”是 ABI 层的约定,不是硬件强制的(见 4.1.1)。硬件不会阻止你拿 ESP 做算术,只是这么干会踩坏栈。

两种书写格式:读题时先认格式

同一条机器指令,两派汇编器写法完全不同。408 真题两种都出现过,所以判据必须清楚。

Intel 格式(王道主用)AT&T 格式(GCC 默认)
操作数顺序mov dst, srcmov src, dst 反的
寄存器eax%eax
立即数8$8
内存操作数[ebp+8]8(%ebp)
复杂寻址[eax+ebx*4+8]8(%eax,%ebx,4)
操作数长度dword ptr [ebp]靠后缀 movl / movw / movb / movq

一眼认格式的判据:有没有 % 和 $。 有就是 AT&T,那么第一个操作数是源;没有就是 Intel,第一个操作数是目的。把方向看反,整道题就全错了,这是读汇编题的第一道关。

两种格式汇编出来的机器码完全相同——书写格式属于汇编器约定,不属于 ISA(4.1.1 已辨析)。

常用指令:按 4.1.5 的五类归位

类指令说明访存改标志位
传送mov搬运看操作数❌
movz / movs零扩展 / 符号扩展后搬看操作数❌
push / pop压栈 / 弹栈(自动改 SP)✅❌
lea取有效地址,见下节❌ 从不访存❌
xchg交换看操作数❌
算术add sub加减看操作数✅
inc dec加 1 / 减 1看操作数✅(不改 CF)
neg取负(求补)看操作数✅
imul / mul有符号 / 无符号乘看操作数✅
idiv / div有符号 / 无符号除看操作数✅
逻辑and or xor not按位运算看操作数✅(not 不改)
移位shl/sal shr sar逻辑左/算术左、逻辑右、算术右看操作数✅
rol ror rcl rcr循环移位(后两个带 CF)看操作数✅
比较cmp做减法,只置标志位看操作数✅
test做与,只置标志位看操作数✅
转移jmp jcc无条件 / 条件转移取指❌(读标志位)
call ret调用 / 返回✅(压/弹栈)❌

三条值得单记的细节:

  • shl 和 sal 是同一条机器指令的两个助记符。 左移时逻辑移位和算术移位的行为完全一样(低位都补 0),所以硬件只需要一条;右移才必须分家(shr 补 0,sar 补符号位)。这一点在 2.2.2 已经推过,这里是它在真实 ISA 上的印证。
  • inc/dec 不改 CF,add/sub 改。 这是为了让它们能安全地用在多精度加法的循环里而不破坏进位链。
  • not 不改任何标志位,neg 改。 因为 not 是纯按位取反,neg 是 的减法。

四个标志位:谁产生,谁消费

标志全称什么时候置 1服务于
CFCarry Flag无符号运算进位/借位无符号比较
OFOverflow Flag有符号运算溢出有符号比较
ZFZero Flag结果为 0相等判断
SFSign Flag结果最高位为 1(负)有符号比较

同一次减法同时置好这四位,是”同一条 cmp 能同时支持有符号和无符号比较”的原因——区别只在随后的 Jcc 去读哪几位。完整的对应表在 4.3.2。

CF 和 OF 的区分,就是 2.2.3 里”无符号溢出”和”有符号溢出”那两个判据,硬件把两个判断电路都做了,把两个结果都存下来,让软件自己挑要哪个。“硬件不做选择,硬件提供全部选项”——这是理解标志位设计的关键一句。

LEA:借用内存操作数的语法写算术

lea 是全章最值得单独拿出来讲的一条指令。

语义只有一句:把有效地址本身送进寄存器,不去那个地址取数。

mov  eax, [ebx+8]     ; eax ← 主存[ebx+8] 里的内容      ★访存
lea  eax, [ebx+8]     ; eax ← ebx+8 这个地址值本身       ★不访存

方括号在 lea 里不代表访存,它只是”借用内存操作数的书写语法来表达一个地址表达式”。这是最容易看错的一点,也是统考唯一考的一点。

疑问点:汇编实验里有个优化, LEA 计算有效地址比 ALU 快,可以用它做一些运算

“能拿来做运算”完全正确,而且这是它在真实编译输出里最常见的用法;“比 ALU 快”这个理由要换掉——赢的不是单条延迟,是指令条数和执行端口。

先说为什么能做运算。 x86 的内存操作数语法本身就是一个三输入表达式:

lea 把这个表达式算出来就交差,于是一条指令顶一串:

lea eax, [ebx + ecx*4 + 8]      ; eax = ebx + 4*ecx + 8

用常规算术指令要四条:

mov eax, ecx
shl eax, 2
add eax, ebx
add eax, 8

再说三个真正的优势(每一条都比”快”这个理由更硬):

优势说明
一条顶四条少取指、少占译码带宽、代码密度高
不影响标志位可以插在 cmp 和 Jcc 之间而不破坏条件
等效三操作数lea eax,[ebx+ecx] 不覆盖 ebx 也不覆盖 ecx;而 add ebx,ecx 是二地址指令,结果必然覆盖第一个操作数(4.1.2)。lea 相当于在二地址的 ISA 里偷到了一条三地址指令。

最后修正”更快”。 lea 走的是地址生成单元 AGU,不是 ALU:

  • 它不占 ALU 的执行端口,因此可以和真正的算术指令同一拍并行发射——赢的是吞吐
  • 但单条延迟并不占优:简单形式(两输入)延迟 1 周期,和 add 一样;复杂形式(三输入 base+index*s+disp)在多数微架构上反而是 3 周期

所以准确的说法是:lea 赢在指令条数、执行端口和不碰标志位;不赢在单条指令的延迟。 这几条全部属于微体系结构层,统考不考,但知道了就不会把”更快”这个错理由写进大题。

顺带一提:lea 是 CISC 设计的一个正面例证——一条复杂指令确实换来了实实在在的代码密度。这条会在 4.4.1 用到。

边界

lea 从不访存。 这是它和 mov 的唯一区别,也是它唯一的考点。判断题若说”lea 需要一次访存取出有效地址处的内容”,错。

操作数顺序看格式,不看直觉。 Intel 的 mov eax, ebx 是 eax ← ebx;AT&T 的 mov %eax, %ebx 是 ebx ← eax。同样的字符串,方向相反。

cmp 和 sub 的差别只在写不写回。 两者置的标志位完全一样。所以 cmp a,b 之后再看标志位,等价于”算了 但把结果扔了”。

x86 不允许两个操作数同时是主存。 mov [eax],[ebx] 是非法的,必须借道寄存器。这是 CISC 也要守的一条限制,因为一条指令里只有一套地址生成通路。RISC 更严格:除 load/store 外没有任何指令能碰主存(4.4.2)。

imul 的单操作数形式和双操作数形式结果去向不同。 单操作数 imul ebx 把 64 位积放进 EDX:EAX;双操作数 imul eax, ebx 只保留低 32 位放进 eax。统考出现的是后者,但看到 EDX:EAX 要知道是前者。

movz 与 movs 的差别是2.1.4 类型转换那一套。 零扩展用于无符号数,符号扩展用于有符号数——扩展方式必须与原类型的符号性一致,否则值会变。这是 C 语言隐式类型转换 bug 在机器级的样子。

统考不要求背助记符,要求读。 遇到没见过的助记符,用四个问题定位它:属于哪一类?访存吗?改标志位吗?改 PC 吗? 这四问在 4.1.5 建立,在这里第一次实战。

对照速查

判断对错
lea 要访存一次❌(从不访存)
lea 里的方括号表示取内容❌(只是地址表达式的语法)
lea 会改标志位❌
lea 比 add 单条延迟更短❌(相同或更长;赢在条数和端口)
Intel 格式第一个操作数是源❌(是目的)
AT&T 格式第一个操作数是源✅
两种格式生成的机器码不同❌(完全相同)
mov 会改标志位❌
inc 会改 CF❌
cmp 会把结果写回目的操作数❌
shl 和 sal 是同一条机器指令✅
shr 和 sar 是同一条机器指令❌(补 0 vs 补符号位)
x86 允许两个主存操作数❌
EAX 和 RAX 是两个独立寄存器❌(同一个的不同宽度视图)

考点

  • lea 取有效地址、不访存、不改标志位——本节第一考点
  • lea 能当算术用,因为内存操作数语法本身是 ;它等效于一条三地址指令
  • Intel mov dst,src / AT&T mov src,dst,靠 % $ 认格式;机器码相同
  • 传送类不改标志位(mov lea push pop);inc/dec 不改 CF;not 不改标志位
  • cmp = 减法不写回,test = 与不写回
  • CF 管无符号溢出,OF 管有符号溢出,硬件两个都算、软件自己挑
  • shl=sal 是一条指令;shr 与 sar 必须分家
  • EAX 是 RAX 的低 32 位;x86 不允许两个主存操作数

链接