我在汇编中使用命令 SBB 做错了什么?

What did I do wrong with the command SBB in assembly?

我的代码有问题,因为

的正确答案

12,341,234 - 22,222,222

-9880988

而我不断从我的程序中得到的答案是

64,750,557

我做错了什么?

.MODEL small
.STACK 100h
.DATA
.CODE
start:
  mov ax, 1234d
  mov bx, 1234d
  mov cx, 2222d
  mov dx, 2222d
  sub ax, cx
  sbb bx, dx
  neg ax
  neg bx
  mov ax, 4c00h
  int 21h
END start

12,341,234 - 22,222,222 -9,880,988 - 到目前为止,还不错。

那么让我们看一下代码:

  mov ax, 1234d
  mov bx, 1234d

这里是第一个问题,从其余代码来看,这两条指令的目的是将值12341234加载到32位寄存器对bx:ax,但是上面写的代码将加载到bx:ax 价值 80,872,658。 (实际上,您可以通过定义 value = bx*10000+ax 的自定义规则将 bx:ax 解释为 12,341,234,但是您执行减法的以下代码不遵守此类规则。所以这个注释只是演示,我的方式得到“80,872,658”对于 CPU 来说是最自然的("native"),但不是唯一可能的 - 对于其余的答案,我们假设所有值都以 "CPU native" 方式进行二进制编码并且然后 1234d:1234d 解释为 80,872,658,对于任何其他自定义规则,您必须编写 lot 更多代码,这将遵守这些规则并根据这些计算基本算法)。

从 CPU 的角度来看:它确实执行机器代码,这是字节值的二进制编码流(它不知道您的文本看起来如何)。要从您的源代码中获取此机器代码,汇编程序会将源代码文本 "mov ax, 1234d" 转换为位“1011_1000 1101_0010 0000_0100”(三个字节,当格式化为十六进制时 "B8 D2 04" 或当格式化为十进制“184 210 4”时,但计算机内存确实使用位,因此二进制表示形式是 CPU 真正操作的内容)。

第一个字节 184 将 CPU 解码为 "this is instruction mov ax,<16 bit immediate> and following two bytes will encode the immediate itself",因此值 210 和 4 表示值 1234。

x86 CPU 是小端类型的机器,所以两个字节“1101_0010 0000_0100”按照这个顺序从内存中读取后,连接成16位值 0000_0100_1101_0010 - "little-endian" 表示第一个字节最不重要,结尾字节最重要,因此它们的顺序相反 (4, 210).

一个字节是8位,因此它可以容纳256种组合(28 = 256,2是每个位的可能状态的数量:0或1,8是数字位)。

如果您以 "human" 的方式写数字,您会像...,8,9,10 ...注意到您突然使用两位数表示值 10。为什么?因为你用尽了所有可用的个位数,所以你将 "upper" 数字(之前隐藏为“0”)+1,并将 "bottom" 数字重置为 0 并重新开始 = 10,11,。 ..,19,20 ... 在用尽底部数字 space 后,你再次撞到上部)。

现在整个字节可以存储 256 种不同的 8 位模式组合,因此如果您将这些位模式解释为从 0 开始的整数,则一个字节可以覆盖整个整数范围 0..255。如果你在 "bytes" 中计数,你将只需要一个字节到值 255,然后你将遇到第一个“10”的情况(耗尽一个字节),你将不得不碰撞 "upper" 字节 +1 并将底部字节重置为 0.

在数学术语中,人类小数形式的数字形式是 v = sum( digitk * 10k ) 对于 k 从 0 向上到 "number of digits - 1"。 IE。 1234 = 1 * 103 + 2 * 102 + 3 * 101 + 4 * 10 0.

bytes类似,但是有256个"digits",多个bytes表示的值是v = sum( bytek * 256k ).

即这两个字节 210 和 4 表示值 210 * 2560 + 4 * 2561 = 210 * 1 + 4 * 256 = 210 + 1024 = 1234.

或者在它的二进制形式中,0000_0100_1101_0010 编码值 1 * 210 + 1 * 27 + 1 * 26 + 1 * 24 + 1 * 21 = 1024 + 128 + 64 + 16 + 2 = 1234

它始终是相同的值,只是编码方式不同,一次是两个 256 位数字“4,210”,或者是四个十进制数字 1,2,3,4,或者是十一个二进制数字 1,0 ,0,1,1,0,1,0,0,1,0 ... 即编码为 base-256、base-10 和 base-2 数字的相同值。

CPU 自然以 2 进制运算,但汇编程序员喜欢使用 16 进制(十六进制)格式的值,因为在十六进制中,每个数字都代表四位(0 = 0000, 1 = 0001, 2 = 0010, ..., D = 1101, E = 1110, F = 1111)。通过一些练习,您可以在头脑中转换特定值的十六进制和二进制格式。

这就是您可能结束编写这两行代码的方式。也许您在某个地方看到过类似 "I have 32 bit value 12345678, so I will load it into two registers as mov bx,1234 mov ax,5678" 的东西 - 只要所有这些数字都是十六进制的,它就可以正常工作!因为此时“5678”实际上是16位的值(四位十六进制数=4*4=16位),恰好是ax的全部大小,所以一旦你用[=24]填充ax =],剩下的部分 1234h 就这样进入 "upper" 16 位寄存器 bx.

但是使用十进制值 12341234 的相同视觉拆分将不起作用。值 12341234 以 16 进制格式化时是 0BC4FF2h,因此使用代码:

mov  ax,4FF2h  ; or ; mov ax,20466  ; or ; mov ax,(12341234 MOD 65536)
mov  bx,00BCh  ; or ; mov bx,188    ; or ; mov bx,(12341234 DIV 65536)
; if your assembler supports MOD/DIV syntax like that...
; then all above are identical instructions resulting into
; the same binary machine code

您正在加载二进制表示形式为 12341234 的寄存器对 bx:ax

即188 * 65536 + 20466 = 12341234。(65536 = 216,因为现在这个值是以 base-65536 编码的,因为 "single digit" 是整个 word在这种情况下,word 是 16 位 => 216 = 65536 种可能的组合。


所以一旦你了解了整数值在计算机中是如何编码的,以及为什么 12341234 由两个字值 188,20466 表示,你将根据以下内容修复 bx:axdx:cx 的初始化到它 (22222222 = 153158Eh = words 153h, 158Eh = 339, 5518 => 即 mov cx,5518 mov dx,339) - 减法将执行此操作:

  sub ax, cx  ; 20466 - 5518 = 14948 (no borrow)
  sbb bx, dx  ; 188 - 339 - 0(no_borrow) = -151 (borrow=yes)

现在这个 -151、14948 字对编码值 -151 * 65536 + 14948 = -9880988。是不是很眼熟?

不幸的是下一部分:

  neg ax
  neg bx

没有多大意义...而单词对 151,-14948 确实 表示值 9880988,在本机 32 位值编码中只有顶部值具有 "sign",所以 151,-14948 实际上被解释为 151,50588,编码值 9946524(与 -9880988 无关)。

要对值 9880988 进行编码,您需要单词对:150、50588 以及通过单独的组件取反二进制值的正确方法(可能 correct ways 之一)是:

  not  ax
  not  bx  ; first "one's complement" is done
  ; "neg" is two's complement, so let's fix it one's to two's
  add  ax,1
  adc  bx,0

再次需要更深入地了解值在计算机中是如何编码的,为什么这样行得通,但让我们至少通过这些值的示例来快速了解...不是(-151, 14948) 是(150, 50587),然后 50587+1 是 50588,没有发生进位,因此 150+0 = 150 -> 字对“150, 50588”,即编码为两个 16 位字的值 9880988。