在汇编中添加两个三字数字
Adding two three-word numbers in assembly
这是我目前所做的:
.MODEL SMALL
.STACK 64
.DATA
X DW 5463h,0F8A8h,0D37Eh
Y DW 87DEh,3408h,92C2h
Result DW 3 DUP(0) ;Trying to add X, Y with the result being here.
.Code
MAIN PROC FAR
MOV AX,@Data
MOV DS,AX
;Adding X,Y:
;Getting Pointers Ready
LEA SI,X
LEA DI,Y
LEA BP,Result
MOV CX,3 ; Counter
CLC ;Clearing the carry
PushF ;We don't want the flag to be affected from the next two operations
;Looping three times to Add Word by Word
WordAdd:
MOV AX,[SI+4] ;We will iterate from the least significant word (4) to the most significant word (0)
MOV BX,[DI+4]
PopF ;Retaining the flag register
ADC AX,BX
PushF
MOV [BP+4],AX
Sub BP,2
Sub SI,2
Sub DI,2
Loop WordAdd
MOV Ax,[BP]
MOV BX,[BP+2]
MOV CX,[BP+4]
MAIN ENDP
END MAIN
不知道为什么最后AX,BX,CX都是0000,虽然我每次调试都确保加法正确并放入AX,但我想不通为什么。
至少有 2 个错误:
a) 最后一个 pushf
永远不会弹出(正如 Nate Eldredge 在评论中提到的那样)。这可能会导致程序在返回其调用者时崩溃。
b) 你的顺序错了。 80x86 是 little-endian,你正在做“最重要的词”。这将使进位错误(如果它是十进制数字而不是 16 位字,它就像“090 + 010 = 001”)。
注意,因为你知道总会有3个单词,而且因为3个很小,所以最好不要循环。这可能看起来像:
MOV AX,[X]
MOV BX,[X+2]
MOV CX,[X+4]
ADD AX,[Y]
ADC BX,[Y+2]
ADC CX,[Y+4]
另请注意,所有现代 80x86 CPU(自 1985 年的 80386 以来)都允许您在 16 位代码中使用 32 位指令。这使您可以做更多类似的事情:
MOV EAX,[X]
MOV BX,[X+4]
ADD EAX,[Y]
ADC BX,[Y+4]
您的代码正确计算了总和并将其存储在 Result
的三个单词中,采用您不寻常的混合字节顺序。但是,请注意 BP
中的地址:您开始将它指向 Result
并在循环的每次迭代中将其递减 2。由于您将循环中的单词存储到 [BP+4]
,因此您得到了正确的单词,但在循环之后 BP
指向 Result-6
。因此,您对 AX, BX, CX
的加载分别取自 Result-6
、Result-4
、Result-2
,它们不是存储总和的位置。
一些更一般的建议:
您当前的设计需要分散在整个代码中的各种魔法偏移量,例如 [BP+4]
、[BP+6]
等。这些都取决于您的数组大小,因此您实际上是在大约 10 个地方对该大小进行硬编码。想一想当您想要修改您的程序以添加四字数字时的痛苦,或者天堂禁止,一个长度要在运行时确定。所以你可能会考虑避免这种情况的设计。例如,如果您从 BP
开始指向最后一个元素而不是第一个元素。
正如 Brendan 所说,您的混合字节顺序是非常规的,可能会使其他程序员感到困惑。颠倒顺序也会简化您的代码,因为您可以向前而不是向后遍历数组。
当您可以简单地执行 ADC AX, [DI+4]
时,像 MOV BX,[DI+4]
/ ADC AX,BX
这样的序列是多余的。 CPU 的设计者为了让每条指令都可用于一个内存操作数而费尽周折;不妨好好利用一下。
LEA SI, X
可以用 MOV SI, OFFSET X
代替,后者做同样的事情,但少使用一个字节。其他 LEA
也一样,只是加载一个常量地址。
请记住,使用 BP
的内存引用隐含地相对于堆栈段 SS
。这对于 DS
和 SS
相等的 SMALL
模型来说很好,但是如果你切换代码模型,你会想知道为什么你的代码没有写入 Result
即使BP
“显然”指向它。
您在循环中使用了四个不同的寄存器 (BP, SI, DI, CX
),它们基本上都只是跟踪循环索引。看看你能不能把它减少到一个。您可以通过执行 MOV AX, [X+BX]
MOV DX, [Y+BX]
.
之类的操作,将单个寄存器用作所有数组的偏移量
如果您最终要转向使用现代 x86 CPUs,LOOP
指令是一个坏习惯,因为它优化不佳并且不鼓励使用它.自己做递减和条件跳转实际上在这样的机器上更有效率,而且它还让你可以自由地使用任何寄存器而不是被绑定到 CX
.
同样,PUSHF/POPF
技巧也是一个坏习惯,因为这些指令在现代 CPUs 上相当昂贵,因为它们可能会操纵中断标志。尝试设计循环的其余部分,以便在迭代之间保持进位标志完好无损。 INC
和 DEC
指令在这里很好,因为它们不修改它;像这样的循环正是它们被设计成那样的原因。
大写您的助记符并始终如一地注册姓名。有些人喜欢全大写,有些人喜欢全小写,但没有人喜欢混合大小写。
这是我目前所做的:
.MODEL SMALL
.STACK 64
.DATA
X DW 5463h,0F8A8h,0D37Eh
Y DW 87DEh,3408h,92C2h
Result DW 3 DUP(0) ;Trying to add X, Y with the result being here.
.Code
MAIN PROC FAR
MOV AX,@Data
MOV DS,AX
;Adding X,Y:
;Getting Pointers Ready
LEA SI,X
LEA DI,Y
LEA BP,Result
MOV CX,3 ; Counter
CLC ;Clearing the carry
PushF ;We don't want the flag to be affected from the next two operations
;Looping three times to Add Word by Word
WordAdd:
MOV AX,[SI+4] ;We will iterate from the least significant word (4) to the most significant word (0)
MOV BX,[DI+4]
PopF ;Retaining the flag register
ADC AX,BX
PushF
MOV [BP+4],AX
Sub BP,2
Sub SI,2
Sub DI,2
Loop WordAdd
MOV Ax,[BP]
MOV BX,[BP+2]
MOV CX,[BP+4]
MAIN ENDP
END MAIN
不知道为什么最后AX,BX,CX都是0000,虽然我每次调试都确保加法正确并放入AX,但我想不通为什么。
至少有 2 个错误:
a) 最后一个 pushf
永远不会弹出(正如 Nate Eldredge 在评论中提到的那样)。这可能会导致程序在返回其调用者时崩溃。
b) 你的顺序错了。 80x86 是 little-endian,你正在做“最重要的词”。这将使进位错误(如果它是十进制数字而不是 16 位字,它就像“090 + 010 = 001”)。
注意,因为你知道总会有3个单词,而且因为3个很小,所以最好不要循环。这可能看起来像:
MOV AX,[X]
MOV BX,[X+2]
MOV CX,[X+4]
ADD AX,[Y]
ADC BX,[Y+2]
ADC CX,[Y+4]
另请注意,所有现代 80x86 CPU(自 1985 年的 80386 以来)都允许您在 16 位代码中使用 32 位指令。这使您可以做更多类似的事情:
MOV EAX,[X]
MOV BX,[X+4]
ADD EAX,[Y]
ADC BX,[Y+4]
您的代码正确计算了总和并将其存储在 Result
的三个单词中,采用您不寻常的混合字节顺序。但是,请注意 BP
中的地址:您开始将它指向 Result
并在循环的每次迭代中将其递减 2。由于您将循环中的单词存储到 [BP+4]
,因此您得到了正确的单词,但在循环之后 BP
指向 Result-6
。因此,您对 AX, BX, CX
的加载分别取自 Result-6
、Result-4
、Result-2
,它们不是存储总和的位置。
一些更一般的建议:
您当前的设计需要分散在整个代码中的各种魔法偏移量,例如
[BP+4]
、[BP+6]
等。这些都取决于您的数组大小,因此您实际上是在大约 10 个地方对该大小进行硬编码。想一想当您想要修改您的程序以添加四字数字时的痛苦,或者天堂禁止,一个长度要在运行时确定。所以你可能会考虑避免这种情况的设计。例如,如果您从BP
开始指向最后一个元素而不是第一个元素。正如 Brendan 所说,您的混合字节顺序是非常规的,可能会使其他程序员感到困惑。颠倒顺序也会简化您的代码,因为您可以向前而不是向后遍历数组。
当您可以简单地执行
ADC AX, [DI+4]
时,像MOV BX,[DI+4]
/ADC AX,BX
这样的序列是多余的。 CPU 的设计者为了让每条指令都可用于一个内存操作数而费尽周折;不妨好好利用一下。LEA SI, X
可以用MOV SI, OFFSET X
代替,后者做同样的事情,但少使用一个字节。其他LEA
也一样,只是加载一个常量地址。请记住,使用
BP
的内存引用隐含地相对于堆栈段SS
。这对于DS
和SS
相等的SMALL
模型来说很好,但是如果你切换代码模型,你会想知道为什么你的代码没有写入Result
即使BP
“显然”指向它。您在循环中使用了四个不同的寄存器 (
之类的操作,将单个寄存器用作所有数组的偏移量BP, SI, DI, CX
),它们基本上都只是跟踪循环索引。看看你能不能把它减少到一个。您可以通过执行MOV AX, [X+BX]
MOV DX, [Y+BX]
.如果您最终要转向使用现代 x86 CPUs,
LOOP
指令是一个坏习惯,因为它优化不佳并且不鼓励使用它.自己做递减和条件跳转实际上在这样的机器上更有效率,而且它还让你可以自由地使用任何寄存器而不是被绑定到CX
.同样,
PUSHF/POPF
技巧也是一个坏习惯,因为这些指令在现代 CPUs 上相当昂贵,因为它们可能会操纵中断标志。尝试设计循环的其余部分,以便在迭代之间保持进位标志完好无损。INC
和DEC
指令在这里很好,因为它们不修改它;像这样的循环正是它们被设计成那样的原因。大写您的助记符并始终如一地注册姓名。有些人喜欢全大写,有些人喜欢全小写,但没有人喜欢混合大小写。