文章目录
- 说明
- [一 汇编基础知识](#一 汇编基础知识)
-
- [1.1 IA32](#1.1 IA32)
- [1.2 程序员可见状态](#1.2 程序员可见状态)
- [1.3 汇编与机器代码定义](#1.3 汇编与机器代码定义)
- [1.4 X86-64整数寄存器](#1.4 X86-64整数寄存器)
- [1.5 数据移动指令(movq)[重点]](#1.5 数据移动指令(movq)[重点])
- [1.6 简单的内存寻址模式[重点]](#1.6 简单的内存寻址模式[重点])
- [1.7 swap函数示例分析[重点]](#1.7 swap函数示例分析[重点])
- [1.8 完整内存寻址模型[重点]](#1.8 完整内存寻址模型[重点])
- [二 算术与逻辑运算](#二 算术与逻辑运算)
-
- [2.1 地址计算指令leaq](#2.1 地址计算指令leaq)
- [2.2 单、双操作数指令](#2.2 单、双操作数指令)
- [2.3 算术运算案例arith](#2.3 算术运算案例arith)
- [三 汇编语言和机器码](#三 汇编语言和机器码)
-
- [3.1 C代码到目标代码的转换过程](#3.1 C代码到目标代码的转换过程)
- [3.2 汇编语言特性:数据类型](#3.2 汇编语言特性:数据类型)
- [3.3 汇编语言特性:操作类型](#3.3 汇编语言特性:操作类型)
- [3.4 处理器状态与条件码](#3.4 处理器状态与条件码)
- [3.5 jx跳转指令](#3.5 jx跳转指令)
- [3.6 while循环编译](#3.6 while循环编译)
说明
- 庄老师的课堂,如春风拂面,启迪心智。然学生愚钝,于课上未能尽领其妙,心中常怀惭愧。
- 幸有课件为引,得以于课后静心求索。勤能补拙,笨鸟先飞,一番沉浸钻研,方窥见知识殿堂之幽深与壮美,竟觉趣味盎然。
- 今将此间心得与笔记整理成篇,公之于众,权作抛砖引玉。诚盼诸位学友不吝赐教,一同切磋琢磨,于学海中结伴同行。
- 资料地址:computing-system-security
一 汇编基础知识
- 复杂指令集计算机 (CISC):Complex Instruction Set Computer
- 精简指令集计算机 (RISC):Reduced Instruction Set Computer
1.1 IA32
-
IA32是Intel于1985年推出的32位x86处理器架构,也称为i386 或x86-32。它是从早期16位8086处理器发展而来的第一个32位版本,支持"平坦寻址"模式,能够运行Unix等现代操作系统。
- IA32是传统x86架构的32位阶段,引入了32位寄存器(如%eax、%ebx)和32位地址空间。
- 它具备复杂的指令集(CISC),但在Linux程序中仅使用其中一小部分。
- 2004年,Intel推出了EM64T技术(几乎与AMD的x86-64相同),将IA32扩展为支持64位操作。
-
简言之,IA32是一种32位x86指令集架构,曾广泛用于PC和服务器,现已被x86-64取代,但仍兼容运行大量32位代码。
1.2 程序员可见状态
- 程序计数器(PC):指向下一条指令地址,x86-64中称为RIP。
- 寄存器文件:存放频繁使用的数据。
- 状态码:记录最近算术或逻辑操作的状态,用于条件跳转。
- 内存:字节可寻址数组,包含代码、用户数据和栈 (支持过程调用)。

1.3 汇编与机器代码定义
- 架构(ISA):编写正确机器/汇编代码所需理解的部分,如指令集、寄存器机器码:处理器执行的字节级程序。
- 汇编代码:机器码的文本表示形式。
- 微架构:架构的具体实现,如缓存大小、核心频率。
- 常见ISA包括Intel的x86、IA32、Itanium、x86-64,ARM (移动设备主流),以及开源RISC-V。
1.4 X86-64整数寄存器
- x86-64 架构下的整数寄存器(CPU 内部用于临时存储整数、地址等数据的高速空间)

- 可以从寄存器的位宽、分类、特殊作用三个角度理解:
- 寄存器的"位宽"与"命名规则" :x86-64 是 64 位 CPU 架构,这些寄存器既可以存 64 位数据,也能兼容 32 位程序(访问"低 32 位")。
- 左列:64 位寄存器的名称,格式为
%rxx(如%rax、%r8)。 - 右列:对应寄存器的 32 位版本(仅访问"低 32 位")
- 左列:64 位寄存器的名称,格式为
- 格式分两种:
- 传统寄存器(如
%rax)的 32 位版本是%eax("e"表示 32 位时代的命名延续); - 新增寄存器(如
%r8)的 32 位版本是%r8d("d"表示 32 位双字,x86-64 新增寄存器的 32 位命名规则)。
- 传统寄存器(如
- 寄存器的"分类":寄存器分为传统继承型和x86-64 新增型。
- 传统继承型(前 8 个:rax、rbx、rcx、rdx、rsi、rdi、rsp、rbp)。这些寄存器在 32 位 x86 时代(如老式 Windows XP、32 位 Linux)就存在,当时是 32 位寄存器(如
%eax、%ebx)。x86-64 时代,它们被扩展为 64 位,名字前加r(如%rax是%eax的 64 位扩展)。 - x86-64 新增型(r8 ~ r15):64 位架构新增的 8 个通用寄存器,32 位时代没有它们,因此 64 位命名为
%r8~%r15,32 位命名为%r8d~%r15d。
- 传统继承型(前 8 个:rax、rbx、rcx、rdx、rsi、rdi、rsp、rbp)。这些寄存器在 32 位 x86 时代(如老式 Windows XP、32 位 Linux)就存在,当时是 32 位寄存器(如
%rsp与%esp:这两个寄存器背景标红,因为它们是栈指针寄存器,负责管理程序的栈空间(函数调用时存储返回地址、局部变量等临时数据的区域):%rsp是 64 位栈指针,64 位程序中用它指向"栈顶";%esp是 32 位栈指针,32 位程序中用它指向"栈顶"。
"压栈(push)"或"弹栈(pop)"指令会自动修改栈指针,让数据有序地存入/取出栈。
- 🧩 X86-64通用寄存器速查表
| 寄存器 | 全称 | 核心作用 | 位宽兼容(低32/16/8位) | 调用约定规则 |
|---|---|---|---|---|
| RAX | Accumulator Register | 函数返回值存储,算术运算(乘除指令默认使用) | EAX / AX / AL/AH | 无需保存(调用者保存) |
| RBX | Base Register | 基址寻址,数据存储(被调用者需备份) | EBX / BX / BL/BH | 需保存(被调用者保存) |
| RCX | Counter Register | 循环计数(如LOOP指令),函数第4参数 | ECX / CX / CL/CH | 无需保存(调用者保存) |
| RDX | Data Register | I/O操作,乘除结果扩展,函数第3参数 | EDX / DX / DL/DH | 无需保存(调用者保存) |
| RSI | Source Index | 字符串操作源地址,函数第2参数 | ESI / SI / SIL | 无需保存(调用者保存) |
| RDI | Destination Index | 字符串操作目的地址,函数第1参数 | EDI / DI / DIL | 无需保存(调用者保存) |
| RBP | Base Pointer | 栈帧基址指针,访问局部变量 | EBP / BP / BPL | 需保存(被调用者保存) |
| RSP | Stack Pointer | 栈顶指针,管理函数调用栈 | ESP / SP / SPL | 需保存(被调用者保存) |
| R8-R15 | - | 新增通用寄存器,函数第5-6参数(R8/R9) | R8D-R15D / R8W-R15W / R8B-R15B | R12-R15需保存,R8-R11无需保存 |
- x86-64架构前6个参数寄存器顺序为rdi、rsi、rdx、rcx、r8、r9,超过6个的参数从右向左入栈
补充说明:表格中"调用约定规则"指函数调用时寄存器值是否需要被调用者备份,违反可能导致数据错误。
- x86-64寄存器通过命名后缀区分访问位宽(64/32/16/8位),仅支持低位分层访问 ,高位不可独立操作。如 不可直接访问高位 (如
%rax的高32位)。 - 传统IA32寄存器(如
%ah、%bh)支持高8位访问(如%ah是%ax的高8位),但x86-64中此类设计已被淘汰,仅保留低位的分层访问。
- IA32寄存器结构与分类:IA32(32位x86架构)寄存器分为通用寄存器和专用寄存器,并保留了对16位和8位模式的向后兼容性。

- 后缀含义 :
- 无后缀 (如
%rax):64位完整寄存器。 d(如%r15d,%eax):低32位(double word)。w(如%ax):低16位(word)。b(如%al):低8位(byte)。
- 无后缀 (如
- 命名层级示例 :
%rax(64位)→%eax(低32位)→%ax(低16位)→%ah(高8位)[IA32支持 x86-64不支持]→%al(低8位)。
- 注:
%r15等新寄存器无%ah类高8位设计。
- 通用寄存器(General Purpose) :共8个,每个32位寄存器可拆分为16位和8位子寄存器(部分),右侧标注了其历史起源(多已过时):
| 32位寄存器 | 16位子寄存器 | 8位高字节 | 8位低字节 | 历史起源(Origin) |
|---|---|---|---|---|
%eax |
%ax |
%ah |
%al |
累加器(accumulate) |
%ecx |
%cx |
%ch |
%cl |
计数器(counter) |
%edx |
%dx |
%dh |
%dl |
数据寄存器(data) |
%ebx |
%bx |
%bh |
%bl |
基址寄存器(base) |
%esi |
%si |
- | - | 源变址寄存器(source index) |
%edi |
%di |
- | - | 目的变址寄存器(destination index) |
- 专用寄存器(Special Purpose) :2个,用于栈操作,整体标红突出。
| 32位寄存器 | 16位子寄存器 | 8位子寄存器 | 功能 |
|---|---|---|---|
%esp |
%sp |
- | 栈指针(stack pointer) |
%ebp |
%bp |
- | 基址指针(base pointer) |
- 兼容性说明
- 16位虚拟寄存器:32位寄存器的低16位(如
%eax的%ax),用于兼容16位模式。 - 8位子寄存器:仅
%eax/%ax、%ecx/%cx、%edx/%dx、%ebx/%bx支持拆分(如%ax拆分为%ah和%al),其他寄存器无8位子寄存器。
- IA32寄存器设计兼顾了向后兼容性(支持16位/8位操作),但现代编程中更常用32位寄存器(如
%eax、%ebx等)。 %esp和%ebp为栈操作核心,%esp指向栈顶,%ebp常用于函数调用时保存栈帧基址。
1.5 数据移动指令(movq)[重点]
- movq基础语法:
movq Source, Dest - 操作类型可以分为立即数、寄存器、内存。
- 立即数:通常为整数数据,以
$开头,如$Ox400、$-520,通常为1、2、4字节编码。 - 寄存器:通常为16个整数寄存器之一,如
%rax,%r13,但%rsp为栈指针寄存器,不可操作。 - 内存:由寄存器给出地址的8个连续字节,如 (%rax)。
- 立即数:通常为整数数据,以
- Intel文档中 mov 指令为 dest, src, 而AT&T语法为 src, dest

- movq请使用的的两大禁忌:不允许内存到内存的直接传输,目标不可以是立即数。
1.6 简单的内存寻址模式[重点]
-
直接寄存器寻址®:内存地址计算为
Mem[Reg[R]],先从寄存器 R 中读取值(Reg[R]),再把这个值作为内存地址,最终访问该地址对应的内存单元(Mem[地址])。如movq (%rcx),%rax实现C语言中的指针解引用。bashmovq (%rcx), %rax- 把寄存器 rcx 中的值作为内存地址,读取该地址处的8字节数据(movq 是"传送四字",对应 64 位),并将数据写入寄存器 rax。

- 把寄存器 rcx 中的值作为内存地址,读取该地址处的8字节数据(movq 是"传送四字",对应 64 位),并将数据写入寄存器 rax。
-
位移寻址 D®:内存地址计算Mem[Reg[R]+D],R为基址寄存器,D为常量偏移量。含义:先从寄存器 R 中读取值(Reg[R],可理解为"基地址"),再加上一个常数位移量 D,最终把 Reg[R]+D 作为内存地址,访问对应内存单元。
bashmovq 8(%rbp), %rdx- 把寄存器 rbp 中的值(基地址)加上 8(位移量 D),得到最终内存地址,读取该地址处的8字节数据,并写入寄存器 rdx。
1.7 swap函数示例分析[重点]
- x86-64架构前6个参数寄存器顺序为rdi、rsi、rdx、rcx、r8、r9,超过6个的参数从右向左入栈。




1.8 完整内存寻址模型[重点]
- 通用形式
D(Rb,Ri,S): M e m [ R e g [ R b ] + S ∗ R e g [ R i ] + D ] Mem[Reg[Rb] + S*Reg[Ri] + D] Mem[Reg[Rb]+S∗Reg[Ri]+D]- D:常量位移 (1、2 或 4 字节)
- Rb:基址寄存器 (任意 16个整数寄存器之一)
- Ri:索引|寄存器(不能是%rsp)
- S:比例因子(1、2、4、8),用于数组访问优化
- 特殊情况:
- (Rb,Ri)= M e m [ R e g [ R b ] + R e g [ R i ] ] Mem[Reg[Rb] + Reg[Ri]] Mem[Reg[Rb]+Reg[Ri]]
- D(Rb,Ri)= M e m [ R e g [ R b ] + R e g [ R i ] + D ] Mem[Reg[Rb] + Reg[Ri] + D] Mem[Reg[Rb]+Reg[Ri]+D]
- (Rb,Ri,S)= M e m [ R e g [ R b ] + S ∗ R e g [ R i ] ] Mem[Reg[Rb] + S*Reg[Ri]] Mem[Reg[Rb]+S∗Reg[Ri]]

二 算术与逻辑运算
2.1 地址计算指令leaq
leaq Src,Dst:将Src表达式的地址加载到Dst寄存器。- leaq指令不进行内存访问,仅计算地址,常用于实现
p=&x[i]类型的指针操作。可以高效计算 x + k × y ( k = 1 , 2 , 4 , 8 ) x+k \times y(k=1,2,4,8) x+k×y(k=1,2,4,8)的算术表达式。 - 例如:将一个数乘以12,编译后使用
leaq (%rdi,%rdi,2),%rax实现× + 2*× = 3x,再通过salg $2,%rax左移两位 (乘以 4)得到12x。
c
long multiple12(long x)
{
return x*12;
}
c
leaq (%rdi,%rdi,2), %rax # t = x+2*x
salq $2, %rax # return t<<2
2.2 单、双操作数指令
- 操作数指令
| 指令格式 | 操作描述 | 备注 |
|---|---|---|
addq Src,Dest |
Dest = Dest + Src |
加法运算 |
subq Src,Dest |
Dest = Dest - Src |
减法运算 |
imulq Src,Dest |
Dest = Dest * Src |
有符号乘法 |
shlq/salq Src,Dest |
Dest = Dest << Src |
逻辑左移/算术左移(等价) |
sarq Src,Dest |
Dest = Dest >> Src |
算术右移(符号位填充) |
shrq Src,Dest |
Dest = Dest >> Src |
逻辑右移(零填充) |
xorq Src,Dest |
Dest = Dest ^ Src |
按位异或 |
andq Src,Dest |
Dest = Dest & Src |
按位与 |
orq Src,Dest |
`Dest = Dest | Src` |
incg Dest |
Dest= Dest+ 1 |
自增 |
decg Dest: |
Dest= Dest - 1 |
自减 |
negq Dest |
Dest =-Dest |
取负值 |
notg Dest |
Dest =~Dest |
按位取反 |
shlq和salq在 x86-64 中功能完全相同,均表示左移。sarq与shrq的区别在于右移时的填充方式(算术右移保留符号位,逻辑右移补零)。- 所有指令均以
q后缀表示 64 位操作数。 - 无符号与有符号区别:整数运算不区分有符号与无符号类型,标志位和后续条件跳转决定语义差异。
2.3 算术运算案例arith

c
// C 源码
long arith(long x, long y, long z) {
long t1 = x + y; // t1 = x + y
long t2 = z + t1; // t2 = z + (x + y)
long t3 = x + 4; // t3 = x + 4
long t4 = y * 48; // t4 = y * 48
long t5 = t3 + t4; // t5 = (x + 4) + (y * 48)
long rval = t2 * t5; // 返回值 = t2 * t5
return rval;
}
c
x in %rdi, y in %rsi, z in %rdx
arith:
leaq (%rdi,%rsi), %rax # t1 <- x + y
addq %rdx, %rax # t2 <- z + t1 = z + x + y
leaq (%rsi,%rsi,2), %rdx # tmp <- y + y*2 = 3y
salq $4, %rdx # t4 <- 3y << 4 = 3y * 16 = y * 48
leaq 4(%rdi,%rdx), %rcx # t5 <- 4 + x + t4 = x + 4 + y*48
imulq %rcx, %rax # rval <- t2 * t5
ret
| 参数 | 寄存器 |
|---|---|
x |
%rdi |
y |
%rsi |
z |
%rdx |
返回值通过 %rax 返回。
- 以"数据流"为主线,逐步追踪变量是如何被计算和复用的。
✅ 第一步:计算 t1 = x + y 和 t2 = z + t1
c
long t1 = x+y;
long t2 = z+t1;
对应汇编:
asm
leaq (%rdi,%rsi), %rax # t1 -> %rax
addq %rdx, %rax # t2 = z + t1 -> %rax
leaq (A,B)是取地址形式,但常用于高效计算A + B。leaq (%rdi,%rsi), %rax等价于%rax = %rdi + %rsi→ 即x + y- 接着
addq %rdx, %rax将z加到%rax上 → 得到t2 = x + y + z
📌 注意:
t1并没有单独保存,而是直接合并进t2的计算中,这是编译器的常见优化(消除中间临时变量)。
✅ 第二步:计算 t4 = y * 48
c
long t4 = y * 48;
对应汇编:
asm
leaq (%rsi,%rsi,2), %rdx # %rdx = y + 2*y = 3y
salq $4, %rdx # %rdx <<= 4 → 3y * 16 = 48y
- 乘法
*48被分解为:48 = 3 × 16 = (1 + 2) × 2^4 - 所以,先用
leaq (%rsi, %rsi, 2)计算y + 2*y = 3y(利用寻址模式实现加法),再左移 4 位:salq $4, %rdx→ 相当于 ×16,最终得到3y × 16 = 48y
⚡ 为何不用
imulq $48, %rsi, %rdx?因为
leaq + shift通常比直接乘法更快,尤其是当乘数是"可分解为移位+加法"的常数时,编译器会自动选择更优路径。
✅ 第三步:计算 t5 = t3 + t4 = (x + 4) + (y * 48)
c
long t3 = x + 4;
long t5 = t3 + t4;
对应汇编:
asm
leaq 4(%rdi,%rdx), %rcx # %rcx = 4 + x + t4
这是一条极其高效的复合寻址指令:
leaq 4(%rdi,%rdx), %rcx表示:%rcx = 4 + %rdi + %rdx- 此时:
%rdi = x、%rdx = t4 = y * 48 - 所以结果就是:
t5 = x + 4 + y*48
💡 编译器再次合并了两个操作(
x+4和+t4)为一条leaq指令 ------ 这正是 x86 中lea指令的强大之处:可用于任意形如a + b + c的算术运算。
✅ 第四步:最终结果 rval = t2 * t5
c
long rval = t2 * t5;
return rval;
对应汇编:
asm
imulq %rcx, %rax # %rax = %rax * %rcx
ret
此时:
%rax中仍保存着t2 = x + y + z%rcx刚刚计算出t5 = x + 4 + 48yimulq %rcx, %rax表示:%rax *= %rcx→ 结果自动留在%rax(即返回值寄存器)
最后 ret 返回。
🔹 手动模拟执行示例
假设输入:x=1, y=2, z=3
| 步骤 | 含义 | 值 |
|---|---|---|
t1 = x + y |
1 + 2 |
3 |
t2 = z + t1 |
3 + 3 |
6 |
t4 = y * 48 |
2 * 48 |
96 |
t5 = x + 4 + t4 |
1 + 4 + 96 |
101 |
rval = t2 * t5 |
6 * 101 |
606 |
验证汇编结果是否一致?
asm
%rdi=1, %rsi=2, %rdx=3
leaq (%rdi,%rsi), %rax → %rax = 1+2 = 3
addq %rdx, %rax → %rax = 3+3 = 6
leaq (%rsi,%rsi,2), %rdx → %rdx = 2 + 4 = 6
salq $4, %rdx → %rdx = 6 << 4 = 96
leaq 4(%rdi,%rdx), %rcx → %rcx = 4 + 1 + 96 = 101
imulq %rcx, %rax → %rax = 6 * 101 = 606
ret → 返回 606 ✅
✅ 完全吻合!
🔹 为什么 leaq 能做算术运算?
leaq(Load Effective Address Quad-word)本来是用来计算内存地址的,但它不访问内存,只做地址计算,因此可以安全用于一般整数加法运算。例如:
asm
leaq (%rax, %rbx, 4), %rcx # rcx = rax + rbx*4
这种结构广泛用于实现指针运算、数组索引、以及上述的快速乘法/加法合成。
因为它不修改标志位(不影响 CF/ZF 等),且通常比 add/imul 更快或可与其他指令并行执行。
三 汇编语言和机器码
3.1 C代码到目标代码的转换过程
- 源文件 p1.c p2.c 经 gcc -Og 编译生成可执行文件 p。

- 编译流程:C 程序 → 汇编程序(.s)→ 目标程序(.o)→ 可执行程序(p)。
- 编译器:gcc -Og -S
- 汇编器:gcc 或 as
- 链接器:gcc 或 ld
- 静态库(.a 文件)参与链接


3.2 汇编语言特性:数据类型
- 整形数据包含1、2、4、8字节类型,可以表示值或地址(无类型指针)。
- SIMD向量是指单指令多数据(Single Instruction, Multiple Data)的向量数据类型,其大小为8、16、32或64字节。它允许处理器通过一条指令同时对多个数据元素执行相同的操作,常用于加速多媒体处理、科学计算和机器学习等领域的并行计算任务。
- 代码:编码一系列指令的字节序列。
- 无聚合类型如数据、结构体,仅仅表现为连续的字节块。
- "无聚合类型"指的是在汇编语言中不直接支持数组或结构体这样的聚合数据类型,所有的数据都被视为连续分配的字节序列。换句话说,像数组或结构体这类复杂的数据组织形式在汇编层面并不存在,而是通过内存中连续的字节来间接实现。
3.3 汇编语言特性:操作类型
- 数据传输:支持内存和寄存器之间移动数据,从内存加载到寄存器(load),从寄存器存储到内存(store)。
- 算术运算:在寄存器上火内存数据上执行算术函数。
3.4 处理器状态与条件码

- 核心组件包括通用寄存器、栈指针、指令指针、条件码标志位。
- 栈指针:
%rsp指向当前栈顶。 - 指令指针:
%rip指向下一条指令地址。 - 条件码标志位:CF、ZF、SF、OF。
- CF(进位标志):无符号运算溢出时置位。
- ZF(零标志):结果为0时置位。
- SF(符号标志):结果为负(最高位为1)时置位。
- OF(溢出标志):有符号运算溢出时置位。
- 注意:leaq不影响条件码
- 显式设置:比较指令(cmpq)
cmpq Src2,Src1→类似计算 Src1- Src2 但不保存结果- CF:若减法需借位则置位(用于无符号比较)。
- ZF:当
Src1 ==Src2时置位 - SF:当
(Src1-Src2)<0时置位。 - OF:检测有符号减法溢出。
- 显式设置:测试指令 (testq),
testq Src2,Src1→执行Src1& Src2但不保存结果- ZF:当按位与结果为0时置位
- SF:当按位与结果为负时置位
- 常见用法:
testq%rax,%rax→检查%rax是否为0或负数。

- setX Dest:根据条件码的组合将目标Dest的低阶字节设置为0或1,不会更改 Dest 的其余 7 个字节。


- Setx指令特性:根据条件码组合设置单个字节,目标必须是可寻址的字节寄存器。不修改其余字节内容,通常配合movzbl指令使用以清零高位,32位操作自动将高位设为0。

- 基础知识(回顾)
- 寄存器(Register):CPU 内部的高速存储单元,比内存快得多。图中涉及的是 x86-64 架构的寄存器(64位系统):
%rdi、%rsi:函数参数寄存器(x86-64 调用约定规定,前几个参数通过寄存器传递,而非栈)。%rax:返回值寄存器(函数的返回值必须放在这里)。%al:%rax的低 8 位(8位寄存器),%eax是%rax的低 32 位(32位寄存器)。
- 函数调用约定:C 语言函数在 x86-64 系统中遵循 System V AMD64 ABI 约定------前 6 个参数依次用寄存器
%rdi、%rsi、%rdx、%rcx、%r8、%r9传递;返回值放在%rax中。 图中函数gt(long x, long y)的参数x用%rdi传递,y用%rsi传递,符合这个规则。
- 解释代码
c
int gt(long x, long y) {
return x > y; // 返回 1(真)或 0(假)
}
目标:比较 x 和 y,如果 x > y 返回 1,否则返回 0。
汇编代码详解
- 第一行汇编代码
c
cmpq %rsi, %rdi # Compare x:y
- 指令含义:比较两个 64 位整数(
q表示 quadword,64位)。 - 操作数:
%rsi是y,%rdi是x。cmp指令格式是cmp 源, 目的,实际计算目的 - 源(即x - y),但不保存结果,只影响标志寄存器(如 ZF 零标志、SF 符号标志、OF 溢出标志等)。 - 作用:通过
x - y的结果设置标志位,后续指令根据标志位判断x > y是否成立。
- 第二行汇编代码
c
setg %al # Set when >
- 指令含义:
setg是"Set if Greater"的缩写,即当x > y时,将目标寄存器设为 1,否则设为 0。 - 目标寄存器:
%al(%rax的低 8 位)。因为set类指令只能操作 8 位寄存器(如%al、%bl等),不能直接操作%rax。 - 判断依据:
setg通过标志寄存器判断x > y(具体是根据 SF、OF、ZF 标志位的组合,无需深究细节,记住setg对应>即可)。 - 举例:若
x=5, y=3,则%al被设为1;若x=2, y=5,%al被设为0。
- 第三行汇编代码
c
movzbl %al, %eax # Zero rest of %rax
- 指令含义:
movzbl是"Move Zero-Extended Byte to Long"的缩写,即把 8 位的%al扩展为 32 位的%eax,高位用 0 填充(Zero-Extended)。 - 为什么需要这一步?:
%al是 8 位,而函数返回值需要放在 64 位的%rax中。如果直接用%al的值作为返回值,%rax的高 56 位可能残留垃圾数据(但实际%eax是%rax的低 32 位,x86-64 中写入%eax会自动将%rax的高 32 位清零)。- 执行
movzbl %al, %eax后,%eax的低 8 位是%al的值(0 或 1),高 24 位被清零;而%rax的高 32 位也会因写入%eax而清零,最终%rax整体是 64 位的 0 或 1,符合 C 语言int类型的返回值要求(int通常是 32 位或 64 位,这里通过扩展确保结果正确)。

ret指令含义:函数返回,将控制权交还给调用者。此时%rax中存放的就是返回值(0 或 1)。
整体流程总结
- 比较
x(%rdi)和y(%rsi),通过cmpq设置标志位; - 用
setg根据标志位判断x > y,结果存到%al(0 或 1); - 用
movzbl将%al扩展为 32 位的%eax,间接确保%rax整体为 0 或 1; ret返回,%rax中的值作为函数返回值。
3.5 jx跳转指令
jX 指令:依据条件码跳转到代码的不同区段,隐式读取条件码。
| jX 指令 | 条件(结果为1) | 描述 |
|---|---|---|
| jmp | 1 | 无条件 |
| je | ZF | 相等 / 零 |
| jne | ~ZF | 不相等 / 非零 |
| js | SF | 负数 |
| jns | ~SF | 非负数 |
| jg | ~(SF^OF) & ~ZF | 大于(有符号) |
| jge | ~(SF^OF) | 大于等于(有符号) |
| jl | SF^OF | 小于(有符号) |
| jle | (SF^OF) | ZF | 小于等于(有符号) |
| ja | ~CF & ~ZF | 高于(无符号) |
| jb | CF | 低于(无符号) |
| >- 说明:ZF(零标志)、SF(符号标志)、OF(溢出标志)、CF(进位标志)为 x86 架构下的条件码(状态标志位);^ 表示异或、& 表示逻辑与、| 表示逻辑或、~ 表示取反。 |
|--------------------------------------------------------------------------------------------------------------|


3.6 while循环编译
- C语言源代码
c
long pcount_do
(unsigned long x) {
long result = 0;
do {
result += x & 0x1;
x >>= 1;
} while (x);
return result;
}
- goto编译的低级代码版本
c
long pcount_goto
(unsigned long x) {
long result = 0;
loop:
result += x & 0x1;
x >>= 1;
if(x) goto loop;
return result;
}

- 寄存器结构
汇编代码对应C语言逻辑,以下是每条指令的功能和执行流程:
| 汇编指令 | 解释 | 对应C代码逻辑 |
|---|---|---|
movl $0, %eax |
将 %eax(result)初始化为0(movl 是32位传送,高位自动清0)。 |
long result = 0; |
.L2: |
循环体入口标签(对应C代码中的 loop:)。 |
loop: |
movq %rdi, %rdx |
将 %rdi(x)的值复制到 %rdx(临时寄存器)。 |
t = x;(临时变量 t 用于计算最低位) |
andl $1, %edx |
%edx(t)与 1 按位与(t & 0x1),结果存回 %edx。 |
t = x & 0x1; |
addq %rdx, %rax |
将 %rdx(t)的值加到 %rax(result)中。 |
result += t; |
shrq %rdi |
%rdi(x)无符号右移1位(x >>= 1,高位补0)。 |
x >>= 1; |
jne .L2 |
若 %rdi(x)不为0(x != 0),则跳回 .L2(循环体入口)。 |
if(x) goto loop; |
rep; ret |
函数返回(ret 指令弹出栈顶值作为返回地址,rep 是冗余前缀,不影响功能)。 |
return result; |
四、执行流程总结
- 初始化:
%rax(result)置0,%rdi保存输入参数x。 - 循环体(.L2):
- 提取
x的最低位(x & 1),通过%rdx临时存储并累加到result。 x右移1位,丢弃已处理的最低位。
- 提取
- 循环条件判断:
jne .L2检查x是否为0。若x != 0,继续循环;否则退出循环,返回result(%rax)。
关键逻辑明
-
无符号右移:
shrq %rdi确保x右移后高位补0(无符号数特性),避免符号位影响循环次数。 -
条件跳转:
jne .L2(jump if not equal)依赖shrq指令对标志位的影响:右移后若x不为0,零标志位(ZF)为0,触发跳转;若x为0,ZF为1,不跳转,退出循环。 -
寄存器复用:通过
%rdx临时存储中间结果(x & 1),避免直接修改%rdi(x)或%rax(result)。 -
C代码中,循环体先执行(
result += x&1和x >>=1),再判断条件(if(x) goto loop),符合 do-while 循环"先执行后判断" 的特性(循环体至少执行一次)。汇编中.L2标签直接对应循环体入口,先执行后跳转,进一步印证了这一点。
