Assembly 通过加法计算倍数和幂的MASM代码

Assembly 通过加法计算倍数和幂的MASM代码,assembly,x86,masm,Assembly,X86,Masm,这是我第一次发帖,如果不完美,我深表歉意。我正在开发一个程序来确定一对整数的和、倍数和指数值。我的代码运行正常,除非在输入中输入0或1,否则程序似乎在CalculatePower过程中循环。我的假设是,这些值导致ECX计数器低于0,但我似乎无法确定如何修复它。它似乎运行得有点慢,所以我也在想,是否有人会有一个建议,使它更有效。谢谢 TITLE Two Integer Calculator (example.asm) ; This program accepts two p

这是我第一次发帖,如果不完美,我深表歉意。我正在开发一个程序来确定一对整数的和、倍数和指数值。我的代码运行正常,除非在输入中输入0或1,否则程序似乎在CalculatePower过程中循环。我的假设是,这些值导致ECX计数器低于0,但我似乎无法确定如何修复它。它似乎运行得有点慢,所以我也在想,是否有人会有一个建议,使它更有效。谢谢

 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