跳转至

Chapter 3 寻址方式

本笔记依据课件《Chapter 3 – Addressing Modes》(共 143 页)整理。内容包括数据寻址、程序控制转移、栈操作,以及数组、结构体和课件附录例题。前置阅读:第 2 章:微处理器体系结构与内存管理。

除特别注明外,代码使用 Intel 操作数顺序,MASM 风格数字和类型标注;不同模式的片段分别说明。示例用来展示指令语义,不是能不加修改地混合编译的完整程序。PDF 页序用于定位原图和原例。

3.1 地址大小与操作数大小

地址大小(Address Size)决定有效地址计算采用的位宽;操作数大小(Operand Size)决定指令读写或运算的数据宽度。 两者不能混为一谈。(第 5–7 页)

典型代码环境 默认地址大小 一般整数指令默认操作数大小
16 位代码 16 位 16 位
32 位代码 32 位 32 位
64 位模式代码 64 位 通常 32 位

前缀可以改变部分默认值,某些指令另有规则。例如 64 位模式的普通 PUSH/POP 通常操作 64 位,而不是套用表中的 32 位默认值。

; 64 位模式
MOV EAX, [RBX]

RBX 提供 64 位地址计算的基址,指令从该地址读 4 字节到 EAX。写 EAX 又会清除 RAX 的高 32 位。因此这条指令同时涉及“64 位地址、32 位数据、64 位寄存器别名”的不同层次。

3.2 指令格式与三类操作数

3.2.1 Intel 语法的方向

load_value: MOV AX, BX ; 将 BX 的值复制到 AX

一行通常包含标号、助记符、操作数和注释。Intel 语法写成 指令 目的, 源,MOV 的数据从右向左复制,源值不因复制而消失。(第 8–14 页)

操作码(Opcode)是机器码中指定操作的部分,MOV 是相应的助记符(Mnemonic);完整机器指令还可能有前缀、寄存器/寻址编码、位移和立即数。

3.2.2 三类操作数

类型 示例 含义
立即数(Immediate) MOV AX,1234H 数值编码在指令中
寄存器(Register) MOV AX,BX 从寄存器中取值
内存(Memory) MOV AX,[BX] 计算地址后从内存取值

在普通 MOV 中,立即数只能作为源;普通两操作数 MOV 也不允许两个显式操作数同时为内存。

MOV AX, BX       ; 复制值
MOV AX, [BX]     ; 读取 BX 指向的内存中的字
MOV [BX], AX     ; 把 AX 中的字写到 BX 指向的内存

MOV EAX,BX 的宽度不匹配。若需要扩展一个较窄的整数,应选择 MOVZX(零扩展)或适用的 MOVSX(符号扩展)形式,例如 MOVZX EAX,BX。

上述源不变的规则是对 MOV 的描述,不能推广为“任何指令的第二操作数都不改变”;例如交换指令有不同语义。

3.2.3 MASM 常量与符号

写法 含义
100 默认十进制 100
100H 十六进制 100,即十进制 256
100B 二进制 100,即十进制 4
0F2H 以 A–F 起始的十六进制数加前导 0
'A' 字符常量
OFFSET array MASM 中取得标号对应的偏移地址

课件第 24 页强调 F2H 可能被当作标识符,应写成 0F2H。多字符立即数的打包规则与汇编器有关;再保存到小端内存时还涉及字节序,初学时更宜逐字节定义字符串。

标识符长度和允许字符由汇编器版本规定。课件中的长度上限和特殊字符列表是特定语法背景,不应当作所有 x86 汇编器的通用规范。

3.3 内存寻址的统一公式

课件第 27–28 页把常见内存寻址归纳为:

\[ \mathrm{EA}=\mathrm{Base}+\mathrm{Index}\times\mathrm{Scale}+\mathrm{Displacement}. \]
  • Base:基址寄存器的值,常用来保存数组或对象起点。
  • Index:变址寄存器的值,常用来保存索引或字节偏移。
  • Scale:比例因子,常见编码允许 1、2、4、8。
  • Displacement:位移,作为常量编码在指令中。

这些项可按编码规则省略,但不是任意组合都合法。16 位地址形式有更严格的寄存器组合限制;缩放变址适用于 386 及以后相应的 32/64 位地址形式。

得到 EA 后,还要按 第 2 章 的规则形成线性地址并进行分页。只有普通平坦段基址为零时,才可直接把 EA 当作线性地址。

3.4 各类数据寻址方式

3.4.1 寄存器与立即数寻址

MOV AX,BX 是寄存器寻址;MOV EAX,13456H 是立即数寻址。后者把常量本身写入 EAX,并不读取地址 13456H 的内存。(第 17–26 页)

指令 EAX 得到什么
MOV EAX,3000H 数值 3000H
MOV EAX,[3000H] 地址表达式为 3000H 的内存中的双字

3.4.2 直接数据寻址

仅使用位移时,\(EA=Disp\)。例如实模式下 DS=1000H:

MOV AL, [1234H]

线性地址为 10000H + 1234H = 11234H,读取其中一个字节。(第 29–38 页)

课件将直接数据寻址细分为累加器专用形式与一般位移形式,主要反映机器编码区别:

16 位地址环境:
MOV AL,[1234H]  → A0 34 12
MOV CL,[1234H]  → 8A 0E 34 12

前者可使用累加器专用的 moffs 编码,后者使用包含 ModR/M 字节的一般形式。它们计算的是同一个内存位置。3 字节、4 字节只是这个例子的长度,换地址大小、寄存器宽度或前缀后会变化;汇编器也可能为等价指令选择另一合法编码。

课件第 34 页的 32 位机器码分类:

机器码对应的指令 课件分类
A1 ...:MOV EAX,[3000H] 累加器专用直接形式
8B 1D ...:MOV EBX,[3000H] 一般位移形式
03 05 ...:ADD EAX,[3000H] 一般位移形式,不能因为用了 EAX 就判成专用 MOV 形式
03 1D ...:ADD EBX,[3000H] 一般位移形式

编译器可用这类地址表达式访问静态对象;符号的最终地址还可能由链接器和装载器确定。64 位代码也常使用 RIP 相对形式。

3.4.3 寄存器间接寻址

寄存器间接寻址(Register Indirect Addressing)把寄存器值当作 EA,而非直接把它当作数据。(第 39–48 页)

; 16 位实模式,DS=0100H,BX=1000H
MOV AX, [BX]

地址为 0100H × 10H + 1000H = 2000H。指令读取 2000H、2001H 两个字节,按小端序组成 AX。

16 位地址形式可使用 [BX]、[BP]、[SI]、[DI],不能写成 [AX] 或 [SP]。[BP] 的编码需要一个零位移,但其寻址语义仍是从 BP 给出的偏移访存。

32 位地址形式可以使用 [EAX]、[EDX]、[ESP] 等;普通 64 位形式可使用 [RAX]、[R8] 等。实模式下使用 32 位地址形式也不会自动解除普通段界限检查。

3.4.4 基址加变址寻址

基址加变址寻址(Base-Plus-Index Addressing)使用 \(EA=Base+Index\)。(第 49–56 页)

; 16 位实模式,DS=0100H,BX=1000H,DI=0010H
MOV DX, [BX+DI]

EA=1010H,线性地址=2010H。如果 BX 指向字节数组开头,DI 表示字节索引,这种形式就自然对应数组访问。

16 位合法双寄存器组合为:

基址 可搭配变址 默认段
BX SI、DI DS
BP SI、DI SS

[BX+BP]、[SI+DI] 都不在合法组合内。索引寄存器中的值是字节偏移;访问字数组的第 \(i\) 个元素时,需先得到 \(2i\),不能把元素编号直接当字节偏移。

3.4.5 寄存器相对寻址

寄存器相对寻址(Register Relative Addressing)使用 \(EA=Register+Disp\),适合访问结构体字段或栈帧局部位置。(第 57–61 页)

; 16 位实模式,DS=0200H,BX=0100H
MOV AX, [BX+1000H]  ; EA=1100H,线性地址=3100H

例如 struct { int a; int b; } 在假设 int 为 4 字节且字段按此布局时,若 EAX 保存结构体地址,MOV EAX,[EAX+4] 可读取成员 b。

3.4.6 基址相对加变址寻址

基址相对加变址寻址(Base Relative-Plus-Index Addressing)使用:

\[ EA=Base+Index+Disp. \]

课件示例 MOV AX,[BX+SI+100H] 将两个寄存器值和常量位移相加。它可以把对象起点、记录偏移、字段偏移分别放在不同项中。(第 62–65、129–130 页)

例如一个结构体有四个 4 字节整数,大小 16 字节,最后一个字段位于偏移 12:

; 32 位模式,EAX=数组起点,ECX=元素索引
SHL ECX, 4              ; ECX = i * 16
MOV EAX, [EAX+ECX+12]   ; 读取第 i 个结构体的最后一个字段

寻址项较多并不意味着一定低效;现代编译器经常用它直接表达对象布局。

3.4.7 缩放变址寻址

缩放变址寻址(Scaled-Index Addressing)支持 \(EA=Base+Index\times Scale+Disp\),Scale 可为 1、2、4、8。(第 66–68 页)

; 32 位模式,EBX 指向 DWORD 数组,ECX 为元素编号
MOV EAX, [EBX+ECX*4]

如果结构体有两个 4 字节字段,总大小 8 字节,第二个字段偏移为 4,则数组成员访问可写作:

MOV EAX, [EBX+ECX*8+4]

机器编码中的 Scale 不能直接写 3、5、16。可以先计算偏移,或用 LEA 组合乘法,例如 [ECX+ECX*2] 可计算 \(3\times ECX\)。

32 位寻址的 ESP 不能作为 SIB 编码中的 index,但可以作为 base。普通 64 位 SIB 形式中 RSP 也不能作为 index,R12 则可通过扩展编码用作 index;不要仅因寄存器名称相似就套用相同限制。

3.5 段选择、宽度与合法性检查

3.5.1 默认段取决于 base

在传统 32 位地址形式中,编码的 base 为 EBP 或 ESP 时,默认段通常是 SS;其他普通 base 默认 DS。关键是编码中的 base 角色,不是只要表达式里出现 EBP 就选 SS。(第 82–84 页)

对于 [EBX+EBP]:

  • 如果编码为 base=EBX、index=EBP,默认 DS。
  • 如果编码为 base=EBP、index=EBX,默认 SS。

数学上的加法可以交换,但编码中的两种角色不同。实际判断应查看汇编器生成的编码;需要特定段时,可使用适用的显式段覆盖(Segment Override),例如 DS:[EBP]。

64 位模式的普通 DS/SS 基址按零处理,但 FS/GS 覆盖仍可引入相应基址。

3.5.2 PTR 标注内存操作数的宽度

MOV [DI], AL          ; 可由 AL 推知写 1 字节
MOV BYTE PTR [DI],10H ; 明确写 1 字节
MOV WORD PTR [DI],10H ; 明确写 2 字节

MOV [DI],10H 无法仅凭立即数确定写多少字节。BYTE/WORD/DWORD/QWORD PTR 是给汇编器的类型说明,不是让 CPU 在运行时进行指针类型转换。(第 44–46 页)

课件的 64 位编码例子:

指令 编码 实际写入
MOV BYTE PTR [RAX],5 C6 00 05 1 字节
MOV WORD PTR [RAX],5 66 C7 00 05 00 2 字节
MOV DWORD PTR [RAX],5 C7 00 05 00 00 00 4 字节
MOV QWORD PTR [RAX],5 48 C7 00 05 00 00 00 8 字节

最后一条使用符号扩展的 32 位立即数写入 64 位内存操作数,不能据此推断普通 MOV r/m64,imm 可以直接写任意 64 位常量。必要时先把常量加载到 64 位寄存器再存储。

PTR 也可用于 INC BYTE PTR [RAX] 等需要消除宽度歧义的场景,不只限于“内存加立即数”的 MOV。

3.5.3 课件合法性选择题

在第 83 页的 32 位地址形式中:

地址表达式 是否合法 原因
[ESP+2] 是 ESP 为 base
[ESP+EBX+2] 是 可令 ESP 为 base、EBX 为 index
[ESP+2*EBX+2] 是 EBX 可以缩放
[2*ESP+EBX+2] 否 ESP 被要求作为缩放 index

判断任何一条汇编时,都应先明确代码模式、地址大小、操作数宽度,再检查寄存器组合和指令支持。

3.6 RIP 相对寻址与规范地址

3.6.1 以“下一条指令”为基准

64 位模式提供 RIP 相对数据寻址(RIP-relative Addressing):(第 69–73、136、138 页)

\[ \mathrm{EA}=\mathrm{RIP}_{\text{next}}+\operatorname{SignExtend}(disp32). \]

基准是当前指令结束后的地址,位移为有符号 32 位数,因此相对范围为 \(-2^{31}\) 到 \(2^{31}-1\) 字节。它适合在代码与数据整体移动时保持相对关系,常用于位置无关代码(Position-Independent Code, PIC)。

课件第 136 页的机器码是:

地址:00007FF778A06490H
机器码:48 8B 05 F9 67 00 00

指令长 7 字节,下一条地址为 00007FF778A06497H。位移的字节按小端排列 F9 67 00 00,数值为 000067F9H:

\[ 00007FF778A06497H+67F9H=00007FF778A0CC90H. \]

从目标地址读取 8 字节 88 13 00 00 00 00 00 00,得到 RAX=1388H=5000。计算访存地址与解码内存数值是两步不同的工作。

RIP 相对形式不能直接再叠加一个普通 SIB index;例如“RIP 加数组索引乘 4”通常先取得数组地址,再使用普通基址加缩放变址。相关地址计算定义可核对 Intel 指令集参考手册。

3.6.2 规范形式只是第一道检查

48 位线性地址模型要求高 16 位复制位 47;57 位模型要求位 63–57 复制位 56。详细例子见 第 2 章。

课件中的 1122334455667788H 不满足普通 48 位或 57 位规范地址规则。即使符合规范,还需要有效页表映射与权限,才能完成访问。课件第 138 页旧示意图出现的 “56-bit” 不应替代前面讲解的五级分页 57 位规则。

3.7 Intel 与 AT&T 语法对照

两种语法可以表达同一条机器指令,主要区别如下。(第 74–79 页)

项目 Intel 风格 AT&T 风格
操作数顺序 目的、源 源、目的
寄存器 eax %eax
立即数 1 $1
内存表达式 [ebx+ecx*4+20] 20(%ebx,%ecx,4)
宽度 寄存器或 BYTE/WORD/DWORD/QWORD PTR 常见后缀 b/w/l/q
32 位复制常量 mov eax,1 movl $1,%eax

示例为 GNU 工具常见 AT&T 写法与 Intel 风格对照:

; Intel
MOV EAX, [EBX+ECX*4+20]
ADD EAX, 10
# AT&T
movl 20(%ebx,%ecx,4), %eax
addl $10, %eax

GCC/Clang 可使用 -masm=intel 选择输出风格。Intel 风格并不意味着 MASM、NASM、GAS 的伪指令、符号寻址和类型语法完全相同;把示例移到另一汇编器时应同时核对这些规则。

3.8 程序控制转移的寻址

程序寻址决定下一条执行哪条指令。它既有“目标怎样给出”的区别,也有“是否改变代码段”的区别,两条分类轴不能混在一起。(第 85–97、137、142–143 页)

3.8.1 相对、寄存器间接与内存间接

形式 示例 目标地址的来源
相对转移 JMP label 下一条指令地址加有符号位移
寄存器间接 16 位 JMP AX AX 中的代码段内目标偏移
内存间接 16 位 JMP WORD PTR [BX] 先读 DS:BX 处保存的目标偏移
直接远转移 概念形式 JMP 1234H:5678H 指令中编码段值/选择子与偏移

JMP BX 与 JMP [BX] 的区别,类似于使用指针值与对指针再解引用:后者首先读取 BX 指向内存中保存的目标地址。跳转表(Jump Table)把多个入口地址放在数组中,再由索引选出目标。

例如 32 位近跳转表的每个条目占 4 字节,可用 JMP DWORD PTR [table+EAX*4] 选取入口;表项格式、索引范围和代码模式必须事先匹配。

3.8.2 短、近、远转移

类型 主要含义
短跳转(Short Jump) 近跳转的一种,使用有符号 8 位位移,范围 -128 到 +127
近跳转(Near Jump) 保持代码段,改变 IP/EIP/RIP
远跳转(Far Jump) 提供段选择子/段值和偏移,重新加载代码段相关状态
任务切换 传统保护模式中的特殊机制,不等同于普通用户程序跳转

近转移可以是相对或间接。16 位和 32 位相对转移常见 rel16、rel32;64 位模式常见 rel8、rel32,不能把“64 位处理器”理解为每条分支都携带 64 位立即地址。

远转移必须遵守段类型和权限检查,并非可以任意跳进任何内存或任意特权级。直接立即远跳转的传统编码也不能直接照搬到 64 位代码中。

3.8.3 相对位移例题

\[ disp=\text{目标地址}-(\text{当前指令起始地址}+\text{指令长度}). \]

若地址 10000H 处是短跳转机器码 EB 03,它长 2 字节,因此目标是:

\[ 10002H+03H=10005H. \]

若要从 10000H 跳回 0FFF0H,位移为 \(-12H=-18\),8 位补码为 EEH,可编码为 EB EE。

课件有时写“当前 IP 加位移”,这里的 IP 应理解为取完当前指令、指向下一条的值,而不是指令起始地址。

3.8.4 直接远地址编码

课件第 137 页的五字节编码:

EA 00 00 00 10

在对应的 16 位环境中,EA 是直接远跳转操作码;接下来两字节为 offset=0000H,最后两字节为 segment=1000H。它表示跳到 1000H:0000H,实模式线性地址为 10000H。

原图用 JMP [10000H] 标注容易被误读成内存间接跳转。这个机器码实际携带的是立即远地址;汇编表示中的逻辑顺序“段:偏移”与机器码中的“先偏移、后段”需要区分。

3.8.5 CALL 与 RET 的联系

近 CALL 在转移控制之前把返回地址压栈,RET 从栈顶取回返回地址。返回地址指向 CALL 后的下一条指令。若不平衡地修改栈指针、覆盖返回地址,返回时就可能跳到错误位置。

远调用和跨特权级转移涉及更多保存内容及检查,不应只按普通近调用的压栈过程推断。

3.9 栈寻址

3.9.1 后进先出与栈指针

栈(Stack)遵循后进先出(Last In, First Out, LIFO)。x86 常规栈向低地址增长,栈指针指向当前栈顶。(第 98–113 页)

对 16 位栈中的普通字操作,可概括为:

PUSH value:SP ← SP - 2;把 value 写入 SS:SP
POP  dest :从 SS:SP 读出字到 dest;SP ← SP + 2

这用于理解普通操作数;像 POP SP、PUSH SP 或以栈指针参与寻址的特殊情况,应再查对应处理器和指令的细节。

3.9.2 手算 PUSH 与 POP

假设实模式 SS=2000H、SP=0100H、AX=1234H,执行 PUSH AX:

  1. SP 变为 00FEH。
  2. 栈顶地址为 20000H+00FEH=200FEH。
  3. 200FEH 保存低字节 34H,200FFH 保存高字节 12H。

再执行 POP BX,BX 得到 1234H,SP 回到 0100H。原内存字节不一定清除,只是逻辑上不再属于当前栈内容。

PUSH AX
PUSH BX
POP CX      ; CX 得到原 BX
POP DX      ; DX 得到原 AX

弹出顺序与压入顺序相反。程序必须保持所需的栈平衡,尤其要避免覆盖 CALL 保存的返回地址。

3.9.3 宽度与模式限制

环境 常见 PUSH/POP 数据宽度 栈指针变化
8086 / 普通 16 位操作 16 位 2 字节
32 位代码 通常 32 位,可有 16 位形式 4 或 2 字节
64 位模式 通常 64 位,也有 16 位形式 8 或 2 字节

没有普通的 8 位 PUSH/POP 数据形式。PUSH imm8 中“8 位”指编码的立即数宽度,它会按指令规则扩展到栈操作宽度;在 64 位模式中也没有普通 PUSH EAX 这样的 32 位寄存器压栈形式。

立即数可以压栈,不能作为 POP 的目的。传统模式可以 PUSH CS,但没有对应的普通 POP CS;64 位模式又对可用段寄存器压栈形式施加额外限制,不能把“所有段寄存器都能压栈”跨模式使用。

3.9.4 PUSHA、POPA 与 FLAGS

16 位 PUSHA 的顺序是:

AX → CX → DX → BX → 原始 SP → BP → SI → DI

共占 16 字节。PUSHAD 保存对应 32 位寄存器,共占 32 字节。保存的是开始执行指令前的 SP/ESP,不是压入几个寄存器后的中间值。

POPA 按 DI、SI、BP、跳过已保存的 SP 槽位、BX、DX、CX、AX 的顺序恢复;POPAD 同理跳过 ESP 槽位。课件第 112、127 页不能理解为会把槽位中的值直接写回 SP/ESP。

PUSHA/POPA/PUSHAD/POPAD 不适用于 64 位模式。PUSHF/PUSHFD/PUSHFQ 与相应 POP 形式处理标志寄存器,但恢复哪些标志位受到运行模式和权限约束,不是任意修改系统标志的方法。

3.9.5 栈的初始化

课件的 MASM 传统段定义:(第 104–107 页)

STACK_SEG SEGMENT STACK
    DW 100H DUP(?)
STACK_SEG ENDS

这里分配 100H 个字,即 200H 字节。传统可执行文件可由汇编、链接与装载信息建立初始栈;裸机代码则需自行准备合法的栈存储、段状态和指针。

课件关于程序段前缀(Program Segment Prefix, PSP)的讨论属于 DOS 环境,不能套用到所有操作系统。有限位宽 SP 的回绕也不代表栈可无限循环使用:越界可能破坏数据,保护模式的界限或分页保护还可能触发异常。

3.10 数组与结构体:把地址公式用起来

3.10.1 同一数组访问可以有不同写法

课件第 133、135 页把字节数组偏移 10H 的元素复制到偏移 20H。假设数组至少有 21H 个字节、DS 已正确初始化:

; 方式一:基址放在 BX,索引放在 DI
MOV BX, OFFSET array
MOV DI, 10H
MOV AL, [BX+DI]
MOV DI, 20H
MOV [BX+DI], AL
; 方式二:数组起点编码在位移中
MOV DI, 10H
MOV AL, array[DI]
MOV DI, 20H
MOV array[DI], AL

两者都经过 AL 中转,因为普通 MOV 不能直接使用两个显式内存操作数。数组若改成字数组,第 10H 个元素的字节偏移就是 20H,不能沿用字节数组的索引解释。

3.10.2 采样循环中的步长

第 131–132 页例题读取传统 PC BIOS 数据区 0000H:046CH,并保存 50 个字。按课件语境整理其核心如下:

; 16 位实模式示意;DS 已指向数据段
; 数据声明:samples WORD 50 DUP(?)
XOR AX, AX
MOV ES, AX
MOV BX, OFFSET samples
MOV CX, 50
again:
    MOV AX, ES:[046CH]
    MOV [BX], AX
    ADD BX, 2
    LOOP again

ES: 指定读源所用的段,写入 [BX] 默认使用 DS。每个样本是 2 字节,因此 BX 每次加 2;数组总共需要 100 字节。课件早先关于“50 字节表”的示意与这里的“50 个字”不能混用。

在该传统 PC 环境中,046CH 对应 BIOS 时钟节拍计数的数据区位置;这里读取的是计数的低字,而不是直接执行 RTC 硬件端口读取。紧密循环也不保证获得 50 个不同节拍值。本例主要展示地址、段覆盖和步长,不能当作已经实现固定间隔采样。

3.10.3 二维数组的地址推导

课件第 134 页给出:

struct foo {
    int a;
    int b;
} tbl[10][10];

假设 int 为 4 字节,结构体大小 8 字节,数组按 C 的行优先(Row-major)顺序布局,则:

\[ \operatorname{addr}(tbl[i][j].b) =\operatorname{base}(tbl)+(10i+j)\times8+4. \]

其中 10i+j 是线性元素编号,乘 8 得到结构体字节偏移,再加 4 选择成员 b。

; 32 位示意:EAX=i,EDX=j
LEA EAX, [EAX+EAX*4]    ; 5*i
LEA EAX, [EDX+EAX*2]    ; j+10*i
MOV EAX, tbl[EAX*8+4]   ; 读取成员 b

加载有效地址(Load Effective Address, LEA)只计算地址表达式的数值,不读取方括号表示的内存,也不进行该地址的数据访存。这里借它完成整数乘加;最后的 MOV 才真正读取数组内容。

结构体大小可能受到对齐和填充(Padding)影响。实际翻译 C 结构时应确认 sizeof 与字段偏移,不能总假设成员大小相加就等于整个对象大小。

3.10.4 MASM 结构体定义

课件第 139–141 页使用 STRUC/STRUCT 与 ENDS 描述记录模板。简化为只含字节字段、无额外填充的示例:

INFO STRUCT
    NAMES  BYTE 32 DUP(?)
    STREET BYTE 32 DUP(?)
    CITY   BYTE 16 DUP(?)
    STATE  BYTE 2 DUP(?)
    ZIP    BYTE 5 DUP(?)
INFO ENDS

person INFO <>
字段 相对记录起点的字节偏移 长度
NAMES 0 32
STREET 32 32
CITY 64 16
STATE 80 2
ZIP 82 5

结构模板描述布局,person 才是分配的实例。字段名称对应偏移,最终访问仍归结为“实例起点加字段偏移”。这些固定长度字段也不会自动成为带终止零的 C 字符串。

课件以 REP STOSB 清空字段。若在传统 16 位环境中 ES 已设为与数据段 DS 相同、DI 指向目标字段,则:

CLD                         ; 地址向高处递增
MOV AX, DS
MOV ES, AX
MOV DI, OFFSET person.NAMES
MOV CX, 32
MOV AL, 0
REP STOSB

每次 STOSB 将 AL 写入 ES:DI,并按 DF 调整 DI;REP 根据计数寄存器重复,最终清零 32 字节。源值 AL、目的 ES:DI、方向 DF 与计数 CX 都是这段代码必须解释的隐含状态。

3.10.5 传统 MASM 内存模型

课件第 117–118 页提到 .MODEL TINY 和 .MODEL SMALL:前者用于传统 DOS 中代码和数据合并的紧凑布局,常见产物为 .COM;后者使用分别的代码段与数据段,常见产物为 .EXE。

.MODEL 属于汇编器与链接布局约定,不是 CPU 从实模式切换到保护模式的指令。现代 64 位工具链也不直接沿用这些 DOS 内存模型。

3.11 课件易错点与练习

需要辨清的问题 正确理解
64 位模式的 AH 是否完全不可用 没有 REX 前缀时仍可编码;课件第 26 页对应注释过于绝对
地址里含 EBP 就一定默认 SS 32 位形式要看编码中的 base
ESP 不能参与任何地址计算 可作 base,不能作普通 SIB index
RIP 相对位移加在指令开头 应加在下一条指令地址
LEA 读取方括号内存 它只计算有效地址数值
PUSH imm8 只消耗一个栈字节 立即数先扩展到对应的栈操作宽度
POPA 把保存的值恢复到 SP 它跳过 SP 槽位
JMP [地址] 等于直接跳到该地址 它从内存取目标;与立即远地址形式不同

第 128 页的教材作业题号:3-10、23、28–32、35、36、45、48、49、53。完整题干需结合指定教材。

以下例题可用于检查计算流程:

条件 核对结果
实模式 DS=1000H、BX=0200H、SI=0010H,读 [BX+SI+4] EA=0214H,线性地址=10214H
32 位平坦段中 EBX=1000H、ECX=3,读 [EBX+ECX*4+8] 地址=1014H,数据宽度仍由指令确定
10000H 处机器码 EB 03 跳转目标=10005H
16 位 SP=0100H,压入两字再弹出一字 SP=00FEH
课件 RIP 例题:下一条=...6497H,位移=67F9H 目标=00007FF778A0CC90H
上述 tbl[2][3].b 相对数组起点的偏移 \((2\times10+3)\times8+4=188\) 字节

3.12 术语表

中文术语 English 缩写 含义
寻址方式 Addressing Mode — 指令指定操作数或控制转移目标的方法
地址大小 Address Size — 有效地址计算使用的位宽
操作数大小 Operand Size — 指令处理的数据宽度
立即数 Immediate — 编码在指令中的常量
位移 Displacement Disp 地址计算中的编码常量
基址寄存器 Base Register — 地址计算中的起点项
变址寄存器 Index Register — 地址计算中的索引项
缩放变址 Scaled-Index Addressing — 对索引乘以 1、2、4、8 后参与地址计算
段覆盖 Segment Override — 用显式段选择替换适用的默认段
RIP 相对寻址 RIP-relative Addressing — 用下一条指令地址加有符号位移寻址
位置无关代码 Position-Independent Code PIC 便于在不同装载地址保持语义的代码
近跳转 Near Jump — 不改变代码段的控制转移
远跳转 Far Jump — 提供段和偏移的控制转移
跳转表 Jump Table — 按索引选择控制转移目标的表
后进先出 Last In, First Out LIFO 栈中最后压入的数据最先弹出
栈帧 Stack Frame — 一次函数调用使用的栈区域
行优先布局 Row-major Layout — 数组同行相邻元素连续存放