在 8086 汇编编程中,我们只能将数据加载到段寄存器中,首先将其加载到通用寄存器中,然后我们必须将其从该通用寄存器移动到段寄存器中。
为什么不能直接加载呢?不被允许有什么特殊原因吗?
mov ax,5000H
和 和有什么不一样mov ax,[5000H]
?是否[5000h]
意味着内存位置 5000h 中的内容?
在 8086 汇编编程中,我们只能将数据加载到段寄存器中,首先将其加载到通用寄存器中,然后我们必须将其从该通用寄存器移动到段寄存器中。
为什么不能直接加载呢?不被允许有什么特殊原因吗?
mov ax,5000H
和 和有什么不一样mov ax,[5000H]
?是否[5000h]
意味着内存位置 5000h 中的内容?
请记住,汇编语言(任何汇编)的语法只是编写机器代码的一种人类可读的方式。您可以在机器代码中执行的操作的规则取决于处理器电子设备的设计方式,而不是汇编程序语法可以轻松支持的内容。
所以,仅仅因为它看起来像你可以写mov DS, 5000h
,而且从概念上看似乎没有你不应该这样做的原因,它实际上是关于“是否有一种机制可以让处理器加载一个段直接从立即数注册?”
在 8086 组件的情况下,我认为原因很简单,工程师只是没有创建可以将信号从内存 I/O 数据线馈送到写入段寄存器的线的电路。
为什么?我有几种理论,但没有权威的知识。
最可能的原因仅仅是简化设计之一:它需要额外的布线和门来做到这一点,而且这是一个不常见的操作(这是 70 年代),它不值得芯片中的空间。这并不奇怪。8086 已经过火,允许将任何普通寄存器连接到 ALU(算术逻辑单元),从而允许将任何寄存器用作累加器。我敢肯定,这样做并不便宜。当时的大多数处理器只允许一个寄存器(累加器)用于该目的。
至于括号,你是对的。假设内存位置 5000h 包含数字 4321h。mov ax, 5000h
将值 5000h 放入 ax 中,同时mov ax, [5000h]
将 4321h 从内存中加载到 ax 中。本质上,括号的作用类似于*
C 中的指针取消引用运算符。
只是为了强调汇编是机器代码可以做什么的理想化抽象这一事实,您应该注意这两种变体不是具有不同参数的相同指令,而是完全不同的操作码。他们本可以使用——比如说——MOV
作为第一个操作码,使用MVD
(MoVe Direct 寻址内存)作为第二个操作码,但他们肯定认为括号语法更容易让程序员记住。
x86 机器码只有一个用于移动到 Sreg 的操作码。该操作码是
8E /r
mov Sreg, r/m16
, 并允许寄存器或内存源(但不是立即的)。
与其他答案中的一些声明相反,mov ds, [5000h]
运行得很好,假设地址处的 2 个字节5000h
为您所在的模式保存了一个有用的段值。(实模式,它们直接用作数字与受保护的 Sreg 值是选择器索引LDT / GDT)。
x86 总是对指令的立即形式使用不同的操作码(将常量编码为机器代码的一部分)与寄存器/内存源版本。例如add eax, 123
从 . 汇编为不同的操作码add eax, ecx
。但是add eax, [esi]
与 的add r, r/m32
操作码相同add eax, ecx
,只是不同的 ModR/M 字节。
NASM 列表,来自nasm sreg.asm -l/dev/stdout
,以 16 位模式组装一个平面二进制文件并生成一个列表。
我手动编辑将字节分成opcode modrm extra
. 这些都是单字节操作码(在 ModRM 字节的 /r 字段中没有额外的操作码位借用空间),因此只需查看第一个字节即可了解它是什么操作码,并注意两条指令何时共享相同的操作码。
address machine code source ; comments
1 00000000 BE 0050 mov si, 5000h ; mov si, imm16
2 00000003 A1 0050 mov ax, [5000h] ; special encoding for AX, no modrm
3 00000006 8B 36 0050 mov si, [5000h] ; mov r16, r/m16 disp16
4 0000000A 89 C6 mov si, ax ; mov r/m16, r16
5
6 0000000C 8E 1E 0050 mov ds, [5000h] ; mov Sreg, r/m16
7 00000010 8E D8 mov ds, ax ; mov Sreg, r/m16
8
9 mov ds, 5000h
9 ****************** error: invalid combination of opcode and operands
支持mov Sreg, imm16
编码需要单独的操作码。这将需要额外的晶体管来为 8086 解码,并且会占用更多的操作码编码空间,从而为未来的扩展留下更少的空间。我不确定 8086 ISA 的架构师认为哪些更重要。
请注意,8086 具有特殊mov AL/AX, moffs
的操作码,当从绝对地址加载累加器时可节省 1 个字节。但是它不能mov
为 Sreg 的 -immediate 保留一个操作码吗?这个设计决策很有意义。您需要多久重新加载一次段寄存器?非常罕见,在真正的大型程序中,它通常不会是常数(我认为)。但是在使用静态数据的代码中,您可能会将累加器加载/存储到循环内的固定地址。(8086 的代码获取非常弱,所以代码大小 = 大多数时间的速度)。
还请记住,您mov Sreg, r/m16
只需一条额外的指令(如 )即可将其用于汇编时常量mov ax, 4321h
。但如果我们只有mov Sreg, imm16
,运行时变量段值将需要自修改代码。(所以显然你不会遗漏r/m16
源版本。)我的意思是,如果你只有一个,那肯定是寄存器/内存源版本。
段寄存器与通用寄存器不同(在硬件级别上)。当然,正如 Mike W 在评论中所说,您不能将立即值直接移动到段寄存器的确切原因只有英特尔开发人员知道。但我想,这是因为这样的设计很简单。请注意,此选择不会影响处理器性能,因为段寄存器操作非常少见。所以,多一条指令,少一条指令根本不重要。
在 x86 汇编语法的所有合理实现中,mov reg, something
将立即数移动something
到寄存器reg
。例如:
NamedConst = 1234h
SomeLabel:
mov edx, 1234h ; moves the number 1234h to the register edx
mov eax, SomeLabel ; moves the value (address) of SomeLabel to eax
mov ecx, NamedConst ; moves the value (1234h in this case) to ecx
关闭方括号中的数字意味着具有该地址的内存内容被移动到寄存器:
SomeLabel dd 1234h, 5678h, 9abch
mov eax, [SomeLabel+4] ; moves 5678h to eax
mov ebx, dword [100h] ; moves double word memory content from the
; address 100h in the data segment (DS) to ebx.
我记得在那天读过原因。我面前没有那个文件,所以请原谅我挥手。
从内存位置或常量加载段寄存器涉及内存周期。如果内存对齐混乱,读取 16 位值可能需要两个内存周期。在周期之间,段寄存器的值是无效的。现在想象一下你在弄乱堆栈段寄存器并且发生了中断:这是你的手推车;享受车程!