为什么我从 mov ax, bx+si+1 得到零?
Why am I getting zero from mov ax, bx+si+1?
mov ax,10
mov bx,4
mov si,ax
mov ax,bx+si+1
LEA ax,[bx+si+1]
当我将 bx,si 和 1 加在一起并移动到 ax 时,结果为 0。
在下一行,当我使用 LEA 时它起作用并且我得到 15.
为什么我在使用 move 时得到零?
您的问题是:"Why am I getting zero from mov ax, bx+si+1?"。很难给你一个准确的答案,因为你忘了告诉你使用的是什么编译器,而且你的代码片段不包含数据段,所以我们看不到你的数据。我们可以做的是用数据段中的一些数字测试您的代码并查看结果:
.model small
.stack 100h
.data
xy db 0A0h,0A1h,0A2h,0A3h,0A4h,0A5h,0A6h,0A7h,0A8h,0A9h,0AAh,0ABh,0ACh,0ADh,0AEh,0AFh,0B0h
.code
mov ax, @data
mov ds, ax
mov ax, 10
mov bx, 4
mov si, ax
mov ax, bx+si+1 ;◄■■ #1 (EXPLANATION BELOW ▼)
LEA ax, [bx+si+1] ;◄■■ #2 (EXPLANATION BELOW ▼)
让我们来说明这里发生了什么:
事情是这样的:
#1 由于存在基址寄存器 (bx
) 和变址寄存器 (si
),总和被解释为内存寻址,因此代码获取内存位置 15 中的数据。ax
寄存器大小为 2 个字节,因此结果是 ax
从内存位置 15 开始获取 2 个字节,在我们的数据段中这 2 个字节分别是 0AFh
和 0B0h
。 al
是ax
的低字节,所以第一个字节(0AFh
)存储在那里,高字节ah
得到第二个字节(0B0h
),这就是 ax
变成 0B0AFh
.
的方式
#2我们说基址寄存器bx
和变址寄存器si
的存在解释为内存寻址,所以[bx+si+1]
指向内存位置 15 (0AFh
)。指令lea
代表load effective address
,其目的是从数据段内部获取一个地址。您的代码行正在获取内存位置 15 (0AFh
) 的有效地址,即 15.
这么多理论需要论证,这里是:
接下来是EMU8086的截图:BLUE箭头指向原始代码行,GREEN箭头指向该行代码被解释(作为内存寻址),RED 箭头显示寄存器 ax
(B0AFh
).[=43= 中的效果]
现在下一条指令的屏幕截图:BLUE 箭头指向原始代码行,GREEN 箭头指向被解释的代码行(注意它与前一个相同),RED 箭头显示寄存器 ax
(0Fh
) 中的效果。
最后,我们来测试一下Visual Studio2013的代码:下一张截图证明mov ax, bx+si+1
是无效的,而另一行给出了与EMU8086相同的结果(ax
=0FH
) :
所以,"why are you getting zero from mov ax, bx+si+1?" 因为您的数据段内的内存位置 15 中可能有一个零。你可能认为 bx+si+1
会给你一个正常的数字,15,但现在你知道使用基址寄存器和变址寄存器将被解释为内存寻址,所以你得不到数字15 但内存位置 15.
中的数据
Jose 详细回答了你的代码中发生了什么,你当然应该尝试用你的 emu8086 复制他的结果,这样你就可以单独调试你的代码了。
我只想添加一个 "high-level" 答案。
您可能有点想念什么是汇编程序。它不是常规的编程语言,更像是目标 CPU.
的实际机器代码的别名
这意味着您编写的任何指令几乎总是 1:1 映射到实际的 CPU 指令(一些汇编器支持所谓的 "pseudo instruction",它们被编译成一些真正的指令定义明确的规则 - 这在 x86 上很少见,我不知道 emu8086 中有这样的伪指令。
并且指令 mov register, some_math_expression
不存在,源值必须是单个数字(在操作码或其他寄存器中编码的立即值,或者从计算机内存中获取的值(您的情况))。
令人困惑的部分是,x86 确实支持非常复杂的内存寻址模式,例如在 32b 模式下 [eax+eax*4+imm32]
是有效的(imm32 = 32b 直接在指令中编码的立即值,这就是地址 "count" 被存储,但在 16b 模式下它只是 16b 数字)。在 16b 模式下,addressing are very limited 的值和寄存器的合法组合,但乍一看它们仍然可能看起来像数学表达式。
但他们不是。它在指令操作码中被硬编码,所以每个合法组合都有自己的二进制数,CPU 不会读作 "some bx register plus some di register plus some number",它读取操作码的第二个字节值(二进制它将 10000001
我想,懒得验证),芯片上的晶体管被设计为该值作为 [bx+di+displacement]
寻址模式工作。
例如inc byte ptr [bx+di+imm16]
(mov
更简单,只需加载值):
CPU读取指令操作码0xFF
= inc
,然后读取寻址模式0x81
(我想,没验证),所以它知道它是“[bx+di+disp]”,然后它再读取另外两个字节,以获得位移值的 16b,最后将这些值相加得到内存中的 16b 偏移量。
然后它获取段值(此指令默认为 ds
,除非段前缀操作码用于覆盖它以用于后续单条指令)并将其添加到偏移量以生成 20b 物理地址内存芯片(请参阅任何实模式寻址文档以了解段+偏移量是如何组合的以及为什么我说的是 20b 而不是 32b)。
然后将这个 20b 值设置在连接到内存芯片的 CPU 的引脚上(通过 "bus" = 很多 "wires" 或 PCB 上的路径,在旧的 8086 上:20寻址,16 个用于数据,还有一些用于 read/write/status 处理),内存芯片被指示从总线上读取的地址加载值(我说的是简单的“8086”计算机,忽略所有重缓存现代 PC 中存在的机制,其中 CPU 不再直接与内存芯片对话)。
一段时间后,内存芯片将设法以这种方式切换其内部状态,总线 "data" 侧的引脚设置为该地址处内存单元中的值,因此 CPU 现在可以 "read" 这些引脚并将其存储到临时未命名寄存器中。然后它对其运行递增过程,并将数据总线引脚设置为新值(总线的地址部分很可能始终保持相同的地址),并指示内存芯片写入该值。
地址 "bx+di+displacement" 的内存中的值就是这样从 6 变成 7 的(例如)。
...我在哪里...
哦,所以它不是自由的数学表达式,而是一种合法的寻址模式,硬连接在 CPU。
不幸的是,emu8086 不会因为缺少 []
而对你大喊大叫,相反它会默默地产生唯一可能的指令,看起来与你写的相似。
顺便说一句,这应该可以让您更容易理解 LEA
指令以及为什么原始语法使用 []
,所以看起来它会访问内存。
lea
是删除了胆量的 mov
指令,所以在内存单元的地址计算到 "mov" 之后它停止了,根本不联系内存芯片,但是用在处理的第一阶段计算的 16b 内存偏移值填充目标寄存器。
mov ax,10
mov bx,4
mov si,ax
mov ax,bx+si+1
LEA ax,[bx+si+1]
当我将 bx,si 和 1 加在一起并移动到 ax 时,结果为 0。 在下一行,当我使用 LEA 时它起作用并且我得到 15.
为什么我在使用 move 时得到零?
您的问题是:"Why am I getting zero from mov ax, bx+si+1?"。很难给你一个准确的答案,因为你忘了告诉你使用的是什么编译器,而且你的代码片段不包含数据段,所以我们看不到你的数据。我们可以做的是用数据段中的一些数字测试您的代码并查看结果:
.model small
.stack 100h
.data
xy db 0A0h,0A1h,0A2h,0A3h,0A4h,0A5h,0A6h,0A7h,0A8h,0A9h,0AAh,0ABh,0ACh,0ADh,0AEh,0AFh,0B0h
.code
mov ax, @data
mov ds, ax
mov ax, 10
mov bx, 4
mov si, ax
mov ax, bx+si+1 ;◄■■ #1 (EXPLANATION BELOW ▼)
LEA ax, [bx+si+1] ;◄■■ #2 (EXPLANATION BELOW ▼)
让我们来说明这里发生了什么:
事情是这样的:
#1 由于存在基址寄存器 (bx
) 和变址寄存器 (si
),总和被解释为内存寻址,因此代码获取内存位置 15 中的数据。ax
寄存器大小为 2 个字节,因此结果是 ax
从内存位置 15 开始获取 2 个字节,在我们的数据段中这 2 个字节分别是 0AFh
和 0B0h
。 al
是ax
的低字节,所以第一个字节(0AFh
)存储在那里,高字节ah
得到第二个字节(0B0h
),这就是 ax
变成 0B0AFh
.
#2我们说基址寄存器bx
和变址寄存器si
的存在解释为内存寻址,所以[bx+si+1]
指向内存位置 15 (0AFh
)。指令lea
代表load effective address
,其目的是从数据段内部获取一个地址。您的代码行正在获取内存位置 15 (0AFh
) 的有效地址,即 15.
这么多理论需要论证,这里是:
接下来是EMU8086的截图:BLUE箭头指向原始代码行,GREEN箭头指向该行代码被解释(作为内存寻址),RED 箭头显示寄存器 ax
(B0AFh
).[=43= 中的效果]
现在下一条指令的屏幕截图:BLUE 箭头指向原始代码行,GREEN 箭头指向被解释的代码行(注意它与前一个相同),RED 箭头显示寄存器 ax
(0Fh
) 中的效果。
最后,我们来测试一下Visual Studio2013的代码:下一张截图证明mov ax, bx+si+1
是无效的,而另一行给出了与EMU8086相同的结果(ax
=0FH
) :
所以,"why are you getting zero from mov ax, bx+si+1?" 因为您的数据段内的内存位置 15 中可能有一个零。你可能认为 bx+si+1
会给你一个正常的数字,15,但现在你知道使用基址寄存器和变址寄存器将被解释为内存寻址,所以你得不到数字15 但内存位置 15.
Jose 详细回答了你的代码中发生了什么,你当然应该尝试用你的 emu8086 复制他的结果,这样你就可以单独调试你的代码了。
我只想添加一个 "high-level" 答案。
您可能有点想念什么是汇编程序。它不是常规的编程语言,更像是目标 CPU.
的实际机器代码的别名这意味着您编写的任何指令几乎总是 1:1 映射到实际的 CPU 指令(一些汇编器支持所谓的 "pseudo instruction",它们被编译成一些真正的指令定义明确的规则 - 这在 x86 上很少见,我不知道 emu8086 中有这样的伪指令。
并且指令 mov register, some_math_expression
不存在,源值必须是单个数字(在操作码或其他寄存器中编码的立即值,或者从计算机内存中获取的值(您的情况))。
令人困惑的部分是,x86 确实支持非常复杂的内存寻址模式,例如在 32b 模式下 [eax+eax*4+imm32]
是有效的(imm32 = 32b 直接在指令中编码的立即值,这就是地址 "count" 被存储,但在 16b 模式下它只是 16b 数字)。在 16b 模式下,addressing are very limited 的值和寄存器的合法组合,但乍一看它们仍然可能看起来像数学表达式。
但他们不是。它在指令操作码中被硬编码,所以每个合法组合都有自己的二进制数,CPU 不会读作 "some bx register plus some di register plus some number",它读取操作码的第二个字节值(二进制它将 10000001
我想,懒得验证),芯片上的晶体管被设计为该值作为 [bx+di+displacement]
寻址模式工作。
例如inc byte ptr [bx+di+imm16]
(mov
更简单,只需加载值):
CPU读取指令操作码0xFF
= inc
,然后读取寻址模式0x81
(我想,没验证),所以它知道它是“[bx+di+disp]”,然后它再读取另外两个字节,以获得位移值的 16b,最后将这些值相加得到内存中的 16b 偏移量。
然后它获取段值(此指令默认为 ds
,除非段前缀操作码用于覆盖它以用于后续单条指令)并将其添加到偏移量以生成 20b 物理地址内存芯片(请参阅任何实模式寻址文档以了解段+偏移量是如何组合的以及为什么我说的是 20b 而不是 32b)。
然后将这个 20b 值设置在连接到内存芯片的 CPU 的引脚上(通过 "bus" = 很多 "wires" 或 PCB 上的路径,在旧的 8086 上:20寻址,16 个用于数据,还有一些用于 read/write/status 处理),内存芯片被指示从总线上读取的地址加载值(我说的是简单的“8086”计算机,忽略所有重缓存现代 PC 中存在的机制,其中 CPU 不再直接与内存芯片对话)。
一段时间后,内存芯片将设法以这种方式切换其内部状态,总线 "data" 侧的引脚设置为该地址处内存单元中的值,因此 CPU 现在可以 "read" 这些引脚并将其存储到临时未命名寄存器中。然后它对其运行递增过程,并将数据总线引脚设置为新值(总线的地址部分很可能始终保持相同的地址),并指示内存芯片写入该值。
地址 "bx+di+displacement" 的内存中的值就是这样从 6 变成 7 的(例如)。
...我在哪里...
哦,所以它不是自由的数学表达式,而是一种合法的寻址模式,硬连接在 CPU。
不幸的是,emu8086 不会因为缺少 []
而对你大喊大叫,相反它会默默地产生唯一可能的指令,看起来与你写的相似。
顺便说一句,这应该可以让您更容易理解 LEA
指令以及为什么原始语法使用 []
,所以看起来它会访问内存。
lea
是删除了胆量的 mov
指令,所以在内存单元的地址计算到 "mov" 之后它停止了,根本不联系内存芯片,但是用在处理的第一阶段计算的 16b 内存偏移值填充目标寄存器。