80x86汇编与C语言
看清一行C代码在CPU、寄存器与内存上经历了什么
按 空格/→ 演示下一步
全部页面点击任意一页,跳回舞台从这页播放
80x86汇编与C语言
看清一行C代码在CPU、寄存器与内存上经历了什么
汇编程序员眼中的系统结构
你写 C 语言时变量在内存里、函数靠栈调用——但汇编程序员看到的更底层:CPU、寄存器、内存、IO 这四样东西如何配合,才是一切指令真正发生的舞台。
CPU=车间主任,寄存器=工作台,内存=仓库按地址取放,I/O=装卸口对接外部
内存模型与数据布局
从 struct 定义出发,沿链路看类型大小、对齐要求、填充策略如何决定内存布局;末端分支说明字节序对多字节值的两种存放约定。
汇编程序的基本结构
一个完整汇编程序由段声明、标签、数据定义、指令序列、入口收尾五部分按序组装而成。
第一条汇编指令:MOV
MOV 是 80x86 最常用指令,下面用 NASM 演示它的五种基本传送形式。
MOV 语法固定为 mov 目的,源;五种组合穷尽合法路径。注意第⑤行必须显式 byte,因内存无宽度信息,汇编器无法从立即数 'Z' 推断要写多大。
指令格式深度解析
上一页的 MOV EAX, EBX 看起来很简单——但 CPU 不认识字母,只认 0 和 1。这条指令翻译成机器码后长什么样?拆开看,是一串按规矩排好的比特。
省/市/区/街道/门牌号各占固定字段,指令各字段也按位置承载不同含义
什么是寻址模式
指令格式那页我们看到操作数可以很复杂,比如 `MOV AX, [BX+SI+8]`。CPU 拿到后怎么知道数在哪?寻址模式就是 CPU 解读'数在哪'的方式。
立即=手里拿着;寄存器=随身包;内存=仓库里按门牌号找
六种寻址模式总览
先看根节点『寻址模式』,再沿六条分支读出每种模式的本质区别;末端方框是最简语法。
立即数寻址
操作数就是常量本身,直接嵌进机器码,CPU 不用再访存取数。
第二个操作数都是常量,编译后直接编码进机器码——CPU 取指令的同时常量就到位。这是最快的模式,因为它根本不用「寻」。
寄存器寻址
看一段最简的寄存器间搬运代码,对照上页的立即数寻址,体会操作数"就在CPU里"的感觉。
L6是立即数寻址做对比;L10、L11的源和目的操作数都是寄存器名,CPU直接从寄存器里读写,根本不走内存。
直接寻址
用 NASM 16 位实模式代码演示直接寻址:地址是常数,机器码里直接编码偏移量。
两行高亮分别是直接寻址的读和写。方括号里 0x0100、0x0300 都是常数偏移,CPU 直接算 DS*16+偏移得物理地址;反汇编能看到偏移被编码进机器码 A1 00 01。
间接寻址
用寄存器的值当内存地址去访问那块内存——C 里的 *p,落到汇编就是 [rax]。
rax 装的是地址而不是数据;[rax] 就是「去 rax 指向的内存位置读/写」——这正是 C 里 *p 的硬件实现。
基址+偏移寻址
一段gcc从C函数生成的汇编:所有局部变量都通过[rbp-N]读写,正是基址加偏移寻址。
rbp是基址寄存器,-4、-8是编码在指令里的常量偏移;同一基址、不同偏移,就对应C里不同的局部变量。
变址寻址
用 NASM 汇编求 dword 数组之和,看 [base+index*scale] 如何精确定位元素。
rcx 是下标,*4 对应 dword 的 4 字节步长;[arr+rcx*4] 等价于 C 的 arr[rcx]。
MOV vs LEA的区别
MOV 的方括号是「取货」,LEA 的方括号是「算数」——同一个语法符号,两套语义。
- 方括号 = 解引用:到内存地址取内容
- CPU 真的产生内存读总线周期
- 结果是变量值,相当于 C 的 *ptr
- 方括号 = 地址表达式:仅算有效地址
- CPU 不产生内存访问周期
- 结果是地址本身,相当于 C 的 &ptr
ADD/SUB与地址运算
看 C 的指针算术如何翻译成 ADD/SUB:加法按 sizeof 缩放,相减再除回元素个数。
`p+2` 在汇编里是 `add eax, 8`(int 占 4 字节,×4=8);两指针相减先 SUB 得字节差,再 `sar eax, 2`(÷4)得元素个数。
通用寄存器全景图
从外往内读嵌套层级;命名规律 R→E→X→H/L 对应 64→32→16→8 位。
32位 vs 64位寄存器
64位不是简单把eax前加个r——子寄存器规则、默认宽度、写入截断都有坑,对追求细节的人尤其需要看清。
- 8个通用寄存器,无r8~r15
- eax→ax→ah/al,高8位独立可寻址
- 默认操作数宽度为32位
- 写入ax不影响eax的高16位
- 16个通用寄存器,含r8~r15
- rax→eax→ax→ah/al,sil/bpl/spl需REX
- 默认操作数宽度为64位(REX.W)
- 写入eax会清零rax的高32位!
寄存器使用惯例
调用者保存vs被调用者保存寄存器
自测检验
关于 LEA eax, [ebx] 和 MOV eax, ebx 的区别,下面哪一项描述正确?
要点回顾
- ✓内存是一条扁平字节序列,指令与数据混居其中
- ✓指令 = 操作码 + 操作数,CPU 是忠实的解码执行器
- ✓六种寻址模式本质是有效地址的六种计算方式
- ✓寄存器是 CPU 的高速工作台,有约定俗成的分工
课后思考
先自己想五分钟,再看参考答案。答案给的是思路,不是唯一写法。
参考答案立即数寻址、寄存器寻址不碰内存,其余四种都走数据总线。差别在「地址怎么算出来」:写死、间接读、基址加偏移量、变址带缩放。
参考答案形参按 ABI 进寄存器或栈帧,局部变量通常按 [rbp+偏移] 即基址加偏移访问。不同优化级别反汇编差异很大,建议自己动手试。
参考答案32 位地址直接放上数据总线,最长寻 4G。64 位需要 REX 前缀才能用 64 位绝对地址,否则高位被截断,只寻低 32 位区间。