80x86汇编与C语言-1

官方信息技术老师·22 页·深入(追求细节与边界)·0 次浏览·2 天前
x86汇编C语言底层程序运行机制寄存器与栈

80x86汇编与C语言

看清一行C代码在CPU、寄存器与内存上经历了什么

按 空格/→ 演示下一步

1 / 22 页

全部页面点击任意一页,跳回舞台从这页播放

x86汇编C语言底层程序运行机制寄存器与栈

80x86汇编与C语言

看清一行C代码在CPU、寄存器与内存上经历了什么

1第 1 页 · 80x86汇编与C语言

汇编程序员眼中的系统结构

你写 C 语言时变量在内存里、函数靠栈调用——但汇编程序员看到的更底层:CPU、寄存器、内存、IO 这四样东西如何配合,才是一切指令真正发生的舞台。

CPU
指令执行者:取指→译码→执行,循环往复
寄存器
CPU 内部的高速暂存空间,指令直接操作它
内存
代码与数据的存放地,按地址访问,比寄存器慢
I/O 端口
CPU 与键盘、屏幕、磁盘等外设通信的通道
工厂车间对应 →系统四部件模型

CPU=车间主任,寄存器=工作台,内存=仓库按地址取放,I/O=装卸口对接外部

2第 2 页 · 汇编程序员眼中的系统结构

内存模型与数据布局

从 struct 定义出发,沿链路看类型大小、对齐要求、填充策略如何决定内存布局;末端分支说明字节序对多字节值的两种存放约定。

图解渲染中…
F1判断当前偏移是否满足字段对齐要求G1padding 不存数据,仅为对齐占用空间K1低地址存低字节,x86/x64 默认采用K2低地址存高字节,常用于网络协议
3第 3 页 · 内存模型与数据布局

汇编程序的基本结构

一个完整汇编程序由段声明、标签、数据定义、指令序列、入口收尾五部分按序组装而成。

1
段声明
用 .data/.code/.stack 划出数据区、代码区、栈区
2
标签定义
给数据或指令地址起名字,供后续指令引用
3
数据定义
用 DB、DW、DD 把常量、字符串、变量写入数据段
4
指令序列
代码段里写真正的 CPU 操作,如 mov、add、call
5
入口与收尾
用 _start 或 main 指定起点,END 伪操作通知汇编器结束
4第 4 页 · 汇编程序的基本结构

第一条汇编指令:MOV

nasm

MOV 是 80x86 最常用指令,下面用 NASM 演示它的五种基本传送形式。

代码高亮加载中…

MOV 语法固定为 mov 目的,源;五种组合穷尽合法路径。注意第⑤行必须显式 byte,因内存无宽度信息,汇编器无法从立即数 'Z' 推断要写多大。

5第 5 页 · 第一条汇编指令:MOV

指令格式深度解析

上一页的 MOV EAX, EBX 看起来很简单——但 CPU 不认识字母,只认 0 和 1。这条指令翻译成机器码后长什么样?拆开看,是一串按规矩排好的比特。

操作码 Opcode
指令的'动词',1~2字节,告诉CPU做什么。MOV、ADD 各有专属编码
ModR/M 字节
Mod(2位)+Reg(3位)+R/M(3位),决定用哪个寄存器、走哪种寻址
SIB 字节
复杂寻址时出现,如 [EBX+ECX*4+8] 中的比例因子 4 由它编码
位移与立即数
指令末尾的常量数据,1/2/4字节可变长,是真正的'数值'载荷
中文通信地址对应 →指令编码格式

省/市/区/街道/门牌号各占固定字段,指令各字段也按位置承载不同含义

Opcode12B  ModR/M1B  SIB1B  Disp+Imm08B  字节\underbrace{\text{Opcode}}_{1\sim2\text{B}}\;\underbrace{\text{ModR/M}}_{\le1\text{B}}\;\underbrace{\text{SIB}}_{\le1\text{B}}\;\underbrace{\text{Disp+Imm}}_{0\sim8\text{B}}\;\text{字节}
6第 6 页 · 指令格式深度解析

什么是寻址模式

指令格式那页我们看到操作数可以很复杂,比如 `MOV AX, [BX+SI+8]`。CPU 拿到后怎么知道数在哪?寻址模式就是 CPU 解读'数在哪'的方式。

立即寻址
操作数就是数据本身,直接写在指令里,如 MOV AX, 100
寄存器寻址
操作数在 CPU 内部寄存器中,如 MOV AX, BX
内存寻址
操作数是个地址,CPU 算出地址后去内存取数
组合寻址
基址+变址+位移自由组合,灵活指向任意内存位置
描述物品的位置对应 →寻址模式

立即=手里拿着;寄存器=随身包;内存=仓库里按门牌号找

7第 7 页 · 什么是寻址模式

六种寻址模式总览

先看根节点『寻址模式』,再沿六条分支读出每种模式的本质区别;末端方框是最简语法。

图解渲染中…
A根:寻址模式回答『操作数从哪儿取、怎么定位』E1方括号=『取该地址里的内容』,寄存器里装的是地址F1基址寄存器+常数位移,结构体成员的硬件原型G1基址+变址寄存器,数组下标运算的硬件原型
8第 8 页 · 六种寻址模式总览

立即数寻址

nasm

操作数就是常量本身,直接嵌进机器码,CPU 不用再访存取数。

代码高亮加载中…

第二个操作数都是常量,编译后直接编码进机器码——CPU 取指令的同时常量就到位。这是最快的模式,因为它根本不用「寻」。

9第 9 页 · 立即数寻址

寄存器寻址

nasm

看一段最简的寄存器间搬运代码,对照上页的立即数寻址,体会操作数"就在CPU里"的感觉。

代码高亮加载中…

L6是立即数寻址做对比;L10、L11的源和目的操作数都是寄存器名,CPU直接从寄存器里读写,根本不走内存。

10第 10 页 · 寄存器寻址

直接寻址

nasm

用 NASM 16 位实模式代码演示直接寻址:地址是常数,机器码里直接编码偏移量。

代码高亮加载中…

两行高亮分别是直接寻址的读和写。方括号里 0x0100、0x0300 都是常数偏移,CPU 直接算 DS*16+偏移得物理地址;反汇编能看到偏移被编码进机器码 A1 00 01。

11第 11 页 · 直接寻址

间接寻址

asm

用寄存器的值当内存地址去访问那块内存——C 里的 *p,落到汇编就是 [rax]。

代码高亮加载中…

rax 装的是地址而不是数据;[rax] 就是「去 rax 指向的内存位置读/写」——这正是 C 里 *p 的硬件实现。

12第 12 页 · 间接寻址

基址+偏移寻址

nasm

一段gcc从C函数生成的汇编:所有局部变量都通过[rbp-N]读写,正是基址加偏移寻址。

代码高亮加载中…

rbp是基址寄存器,-4、-8是编码在指令里的常量偏移;同一基址、不同偏移,就对应C里不同的局部变量。

13第 13 页 · 基址+偏移寻址

变址寻址

nasm

用 NASM 汇编求 dword 数组之和,看 [base+index*scale] 如何精确定位元素。

代码高亮加载中…

rcx 是下标,*4 对应 dword 的 4 字节步长;[arr+rcx*4] 等价于 C 的 arr[rcx]。

14第 14 页 · 变址寻址

MOV vs LEA的区别

MOV 的方括号是「取货」,LEA 的方括号是「算数」——同一个语法符号,两套语义。

MOV 读取数据
  • 方括号 = 解引用:到内存地址取内容
  • CPU 真的产生内存读总线周期
  • 结果是变量值,相当于 C 的 *ptr
LEA 计算地址
  • 方括号 = 地址表达式:仅算有效地址
  • CPU 不产生内存访问周期
  • 结果是地址本身,相当于 C 的 &ptr
看方括号前先看指令:MOV 解引用读数据,LEA 不解引用算地址。
15第 15 页 · MOV vs LEA的区别

ADD/SUB与地址运算

x86asm

看 C 的指针算术如何翻译成 ADD/SUB:加法按 sizeof 缩放,相减再除回元素个数。

代码高亮加载中…

`p+2` 在汇编里是 `add eax, 8`(int 占 4 字节,×4=8);两指针相减先 SUB 得字节差,再 `sar eax, 2`(÷4)得元素个数。

16第 16 页 · ADD/SUB与地址运算

通用寄存器全景图

从外往内读嵌套层级;命名规律 R→E→X→H/L 对应 64→32→16→8 位。

图解渲染中…
raxR 前缀:64 位寄存器,x86-64 时代新增eaxE 前缀:Extended 32 位,386 引入ax无前缀:原始 16 位,8086 时代alAL/AH 是 AX 的两个独立 8 位切片
17第 17 页 · 通用寄存器全景图

32位 vs 64位寄存器

64位不是简单把eax前加个r——子寄存器规则、默认宽度、写入截断都有坑,对追求细节的人尤其需要看清。

32位寄存器
  • 8个通用寄存器,无r8~r15
  • eax→ax→ah/al,高8位独立可寻址
  • 默认操作数宽度为32位
  • 写入ax不影响eax的高16位
64位寄存器
  • 16个通用寄存器,含r8~r15
  • rax→eax→ax→ah/al,sil/bpl/spl需REX
  • 默认操作数宽度为64位(REX.W)
  • 写入eax会清零rax的高32位!
64位兼容32位,但写32位子寄存器会截断高32位。地址/指针必须64位;r8~r15需REX前缀。
18第 18 页 · 32位 vs 64位寄存器

寄存器使用惯例

调用者保存vs被调用者保存寄存器

寄存器使用惯例
调用者保存vs被调用者保存寄存器
19第 19 页 · 寄存器使用惯例

自测检验

点击作答

关于 LEA eax, [ebx] 和 MOV eax, ebx 的区别,下面哪一项描述正确?

20第 20 页 · 自测检验

要点回顾

  • 内存是一条扁平字节序列,指令与数据混居其中
  • 指令 = 操作码 + 操作数,CPU 是忠实的解码执行器
  • 六种寻址模式本质是有效地址的六种计算方式
  • 寄存器是 CPU 的高速工作台,有约定俗成的分工
延伸主题:标志寄存器与条件跳转栈帧与函数调用约定移位与乘除指令深入
21第 21 页 · 要点回顾

课后思考

先自己想五分钟,再看参考答案。答案给的是思路,不是唯一写法。

1六种寻址模式中,哪几种真正访问内存?和寄存器寻址的本质差别在哪?

参考答案立即数寻址、寄存器寻址不碰内存,其余四种都走数据总线。差别在「地址怎么算出来」:写死、间接读、基址加偏移量、变址带缩放。

2写一个 sum(int a, int b) C 函数,gcc -O0 编译后汇编会用上哪几种寻址模式?

参考答案形参按 ABI 进寄存器或栈帧,局部变量通常按 [rbp+偏移] 即基址加偏移访问。不同优化级别反汇编差异很大,建议自己动手试。

332 位 MOV [addr], AL 与 64 位同名指令,硬件解码和地址送上总线时各自有什么不同?

参考答案32 位地址直接放上数据总线,最长寻 4G。64 位需要 REX 前缀才能用 64 位绝对地址,否则高位被截断,只寻低 32 位区间。

22第 22 页 · 课后思考