Assembly 通过加法计算倍数和幂的MASM代码
这是我第一次发帖,如果不完美,我深表歉意。我正在开发一个程序来确定一对整数的和、倍数和指数值。我的代码运行正常,除非在输入中输入0或1,否则程序似乎在CalculatePower过程中循环。我的假设是,这些值导致ECX计数器低于0,但我似乎无法确定如何修复它。它似乎运行得有点慢,所以我也在想,是否有人会有一个建议,使它更有效。谢谢Assembly 通过加法计算倍数和幂的MASM代码,assembly,x86,masm,Assembly,X86,Masm,这是我第一次发帖,如果不完美,我深表歉意。我正在开发一个程序来确定一对整数的和、倍数和指数值。我的代码运行正常,除非在输入中输入0或1,否则程序似乎在CalculatePower过程中循环。我的假设是,这些值导致ECX计数器低于0,但我似乎无法确定如何修复它。它似乎运行得有点慢,所以我也在想,是否有人会有一个建议,使它更有效。谢谢 TITLE Two Integer Calculator (example.asm) ; This program accepts two p
TITLE Two Integer Calculator (example.asm)
; This program accepts two positive integers and calculates a sum, product, and power from the integers
INCLUDE Irvine32.inc ; Using procedure calls ReadInt, WriteInt, and WriteString
.data
ReadInt proto
WriteInt proto
WriteString proto
prompt BYTE "Enter a positive integer: ",0 ; Text to prompt user for a positive integer
sum BYTE "The sum is: ",0 ; Text preceeding the sum result
product BYTE 0dh, 0ah, "The product is: ",0 ; Text preceeding the product result
power BYTE 0dh, 0ah, "The power result is: ",0 ; Text preceeding the power value result
spacer BYTE 0dh,0ah, " ",0dh,0ah,0 ; Takes the place of procedure call Crlf to place space between program and wait message
integer DWORD ? ; The user-entered integer
.code
main PROC
; Asks for and receives the first integer from the user, moves it to EBX
call GetInteger
mov ebx,eax
; Receives the second integer from the user and moves it into the 'integer' variable
call GetInteger
mov integer,eax
; Adds the values in the EAX and EBX registers together, then displays the sum
call AddNumbers
mov edx,OFFSET sum
call WriteString
call WriteInt
; Reloads the original user-entered integer to the EAX register, multiplies the EAX and EBX reigsters, then displays the product
mov eax,integer
call MultiplyNumbers
mov edx,OFFSET product
call WriteString
call WriteInt
; Reloads the original user-entered integer to the EAX register, then calculates the value stored in EAX to the power of the value in the EBX register
mov eax,integer
call CalculatePower
mov edx,OFFSET power
call WriteString
call WriteInt
; Inserts a line of separation between program and the wait message
mov edx,OFFSET spacer
call WriteString
exit
main ENDP
;-----------------------------------------------------------
GetInteger PROC
; Prompts the user to enter an integer, assuming that the
; user will enter valid, positive integers. Stores the
; user-entered integer in EAX
; Receives:
; Returns: EAX
;-----------------------------------------------------------
mov edx, OFFSET prompt ; Prompts the user to enter an (assumed positive) integer
call WriteString ; Displays the string prompt to enter a positive integer
call ReadInt ; Stores the user-input integer in EAX
ret
GetInteger ENDP ; End of UDP GetInteger
;-----------------------------------------------------------
AddNumbers PROC
; Accepts two integers in EAX and EBX then adds the two
; integers together, storing the sum in EAX.
; Receives: EAX, EBX
; Returns: EAX
;-----------------------------------------------------------
add eax,ebx ; adds the two integers together, sum is stored in EAX
ret
AddNumbers ENDP
;-----------------------------------------------------------
MultiplyNumbers PROC USES ecx
; Accepts two integers in EAX and EBX. Uses the AddNumbers
; procedure to multiply the two numbers, then stores the
; product in EAX.
; Receives: EAX, EBX
; Returns: EAX
;-----------------------------------------------------------
mov ecx,eax
dec ecx
mov eax,ebx
multiply:
call AddNumbers
loop multiply
ret
MultiplyNumbers ENDP
;-----------------------------------------------------------
CalculatePower PROC
; Accepts two integers in EAX and EBX, then uses the
; MultiplyNumbers procedure to calculate the integer in EAX
; to the power of the integer in EBX. The result is stored
; in the EAX register.
; Receives: EAX, EBX, ECX
; Returns: EAX
;-----------------------------------------------------------
mov ecx,eax
dec ecx
mov eax,ebx
exponent:
call MultiplyNumbers
loop exponent
ret
CalculatePower ENDP
END main
虽然问题中没有明确提到,但您在评论中确认,作业要求您只使用加法和减法。否则,您肯定会希望使用
mul
/imul
说明进行此操作。它们比加法/减法慢(效率低),但比循环和重复加法快得多!更不用说,它们使代码更易于阅读和编写,从而减少了错误。有了这些规定,让我们看看您编写的核心函数
这实际上是一个很好的开始。您正在使用调用约定所需的所有信息对函数定义进行注释,包括传入哪些寄存器参数以及返回结果的寄存器。太多刚开始的汇编语言程序员不这样做,他们的代码会变得难以理解
AddNumbers
过程写起来是正确的,但是因为它非常简单(除了ret
之外只有一条指令),所以我会把它写成一个宏。如果你知道C,宏就像是一个预处理器宏:汇编程序基本上只是把宏中的东西复制粘贴到你使用它的地方。基本文本替换。这是非常强大的,因为它节省了函数调用的开销(不需要调用
或ret
),但您希望节省使用宏,因为它们也会使代码膨胀。不过,指令不到3条,对于宏来说,这是一个明显的例子。但也许你还没有学会宏,不应该使用它们。如果没有,请忽略此建议。:-)
如果其中一个输入为0或1,则不会返回正确的结果 如果赋值保证输入为正整数,那么从数学上讲,0不是正整数,因此您不必担心这种情况。(在实际代码中,我想添加一个断言来验证输入是否为非零,就像只在调试/测试构建中有条件地包含的测试一样。) 但是您仍然需要担心输入1。为了了解原因,让我们通过一个示例来思考代码流。假设
EAX
为1,EBX
为4。这应该返回4,因为1×4=4。不幸的是,它实际上返回8。计数器ECX
设置为0,但由于在检查计数器之前调用了AddNumbers
(通过循环
指令),AddNumbers
始终运行一次。要解决这个问题,您需要在进入循环之前检查计数器
如果您知道C,那么现在这里基本上是一个do{…}while(condition)
循环。您需要的基本上是一个while(condition){…}
循环。需要在循环的顶部而不是底部检查条件
正确的代码如下所示:
mov ecx, eax
dec ecx ; ECX = (EAX - 1)
jz finished ; bail out early if ECX == 0
mov eax, ebx ; EAX = EBX
multiply:
call AddNumbers ; EAX += EBX
loop multiply ; decrement counter and keep looping if != 0
finished:
ret
除了应该避免使用非常慢的循环
指令之外。相反,将其替换为等效的扩展形式(DEC
+JNZ
),这将大大加快:
mov ecx, eax
dec ecx ; ECX = (EAX - 1)
jz finished ; bail out early if ECX == 0
mov eax, ebx ; EAX = EBX
multiply:
call AddNumbers ; EAX += EBX
dec ecx ; decrement counter
jnz multiply ; keep looping if counter != 0
finished:
ret
这是一种有点违反直觉的情况,更多的指令实际上会导致更快的代码。你的老师可能教你循环,因为它在概念上很简单,但现实世界中没有任何代码使用它,无论是由汇编语言程序员编写的还是由编译器生成的,因为扩展形式要快得多。您基本上可以忘记循环
指令甚至存在。它只在优化大小时才有用,这是很少做到的,尤其是在手工编写汇编代码时
另一个额外的优化技巧是如下代码:
mov ecx, eax
dec ecx
有时可以替换为,它在一个步骤中移动和减小:
lea ecx, [eax - 1]
由于复杂的原因,这在现实世界中是否真的会更快是不一样的,但不会再慢了,这是一个有用的诀窍。(编译器喜欢使用它,就像专家程序集黑客一样。)唯一需要注意的是,LEA
不像DEC
那样设置标志,这就是为什么您不能在这里使用它(您需要设置标志以便有条件地分支-JZ
,JNZ
,等等)
如果您按照我之前的建议,将AddNumbers
函数转换为宏,则汇编程序将生成以下代码:
mov ecx, eax
dec ecx ; ECX = (EAX - 1)
jz finished ; bail out early if ECX == 0
mov eax, ebx ; EAX = EBX
multiply:
add eax, ebx ; inline expansion of AddNumbers macro
dec ecx ; decrement counter
jnz multiply ; keep looping if counter != 0
finished:
ret
这是你将要得到的最佳结果,而不是(A)能够使用乘法指令,或者(B)展开循环(从而使代码更加复杂,增益非常小)。循环的必要性不可避免地会使这个代码狗变慢。这是一个很好的教训,微观优化只能走这么远。如果您选择了一个糟糕/低效的算法,那么无论您如何尝试和调整它,您都注定要降低代码的速度
这里的bug本质上与我们在
MultiplyNumbers
中发现的bug相同,除了CalculatePower
调用MultiplyNumbers
这一事实之外。幸运的是,解决方法也是一样的
另外,你的计数器是根据错误的输入建立的。如果我们计算的是EAX
EBX
,那么我们希望计数器以EBX
为基础
mov ecx, ebx
dec ecx ; ECX = (EBX - 1)
jz finished ; bail out early if ECX == 0
mov ebx, eax ; EBX = EAX
exponent:
call MultiplyNumbers ; EAX *= EBX
dec ecx ; decrement counter
jnz exponent ; keep looping if counter != 0
finished:
ret
到
mov ecx, eax
dec ecx ; ECX = (EAX - 1)
jz finished ; bail out early if ECX == 0
mov eax, ebx ; EAX = EBX
multiply:
add eax, ebx ; inline expansion of AddNumbers macro
dec ecx ; decrement counter
jnz multiply ; keep looping if counter != 0
finished:
ret
;-----------------------------------------------------------
CalculatePower PROC
; Accepts two integers in EAX and EBX, then uses the
; MultiplyNumbers procedure to calculate the integer in EAX
; to the power of the integer in EBX. The result is stored
; in the EAX register.
; Receives: EAX, EBX, ECX
; Returns: EAX
;-----------------------------------------------------------
mov ecx,eax
dec ecx
mov eax,ebx
exponent:
call MultiplyNumbers
loop exponent
ret
CalculatePower ENDP
mov ecx, ebx
dec ecx ; ECX = (EBX - 1)
jz finished ; bail out early if ECX == 0
mov ebx, eax ; EBX = EAX
exponent:
call MultiplyNumbers ; EAX *= EBX
dec ecx ; decrement counter
jnz exponent ; keep looping if counter != 0
finished:
ret
a^3 = (a * a * a)
a^7 = (a^3) * (a^3) * a
a^15 = (a^7) * (a^7) * a
; Calculates EAX^EBX via exponentiation by squaring.
; Returns result in EAX.
; Clobbers: EBX, ECX
mov ecx, 1 ; ECX holds result; initialize to 1
test ebx, ebx ; is the exponent zero?
jz finished ; if so, skip everything and return result (1)
check_even:
test ebx, 1 ; test lowest bit (sets flags according to result of EBX % 2)
jz skip ; skip next instruction if exponent is even (EBX % 2 == 0)
imul ecx, eax ; result *= base
skip:
imul eax, eax ; base *= base
shr ebx, 1 ; exponent /= 2
jnz check_even ; keep looping if exponent != 0
finished:
mov eax, ecx ; put result in EAX
ret
a^2 = (a * a)
a^3 = (a^2) * a
a^6 = (a^3) * (a^3)
a^12 = (a^6) * (a^6)
a^15 = (a^12) * (a^3)
unsigned int To15thPower(unsigned int a)
{
return (a * a * a *
a * a * a *
a * a * a *
a * a * a *
a * a * a);
}
To15thPower(unsigned int): ; parameter is in EAX
mov edx, eax
imul edx, eax
imul edx, eax
imul edx, edx
imul edx, eax
imul edx, edx
imul eax, edx
ret
unsigned int To15thPower_Optimized(unsigned int a)
{
unsigned int a2 = a * a;
unsigned int a3 = a2 * a;
unsigned int a6 = a3 * a3;
unsigned int a12 = a6 * a6;
return a12 * a3;
}
To15thPower_Optimized(unsigned int): ; parameter is in EAX
mov edx, eax
imul eax, edx
imul eax, edx
mov edx, eax
imul edx, eax
imul edx, edx
imul eax, edx
ret