通过堆栈从过程返回一个值

时间:2015-04-20 20:21:53

标签: assembly stack procedure

我正在学习汇编,我必须编写一个程序(函数)来获取一个数字并返回1如果它是偶数而0如果它是&#39 ;不是。

我不得不通过寄存器或标志返回答案,而是通过堆栈(例如我无法将答案放在bxax中并检查它们的值程序)。我怎么能这样做?

3 个答案:

答案 0 :(得分:3)

下一个程序是使用EMU8086 Intel语法(只需复制,粘贴和运行),这就是它的作用:显示一条消息,从键盘捕获一个数字,将数字从字符串转换为数字,检查数字是否为偶数或奇数,堆栈中存储“1”或“0”,并根据“1”或“0”显示消息。在这里,有很多评论可以帮助您理解:

.stack 100h
;------------------------------------------
.data
msj1  db 13,10,'Enter the number: $'
msj2  db 13,10,'The number is even$'
msj3  db 13,10,'The number is odd$'
str   db 6 ;MAX NUMBER OF CHARACTERS ALLOWED (5).
      db ? ;LENGTH (NUMBER OF CHARACTERS ENTERED BY USER).
      db 6 dup (?) ;CHARACTERS ENTERED BY USER. 
;------------------------------------------
.code          
;INITIALIZE DATA SEGMENT.
  mov  ax, @data
  mov  ds, ax

;DISPLAY MESSAGE.
  call clear_screen  ;DECLARED AT THE END OF THIS CODE.
  mov  ah, 9
  mov  dx, offset msj1
  int  21h

;CAPTURE NUMBER FROM KEYBOARD AS STRING.
  mov  ah, 0Ah
  mov  dx, offset str
  int  21h

;CONVERT CAPTURED NUMBER FROM STRING TO NUMERIC.
  mov  si, offset str ;PARAMETER FOR STRING2NUMBER.
  call string2number ;NUMBER RETURNS IN BX. 

;CALL PROC TO FIND OUT IF NUMBER IS EVEN OR ODD. THE INSTRUCTION
;"CALL" WILL PUSH IN STACK THE ADDRESS OF THIS INSTRUCTION, THAT'S 
;HOW IT KNOWS HOW TO COME BACK HERE TO CONTINUE EXECUTION.
  call even_or_odd

;GET RESULT FROM STACK.
  pop  ax  

;DISPLAY RESULT.
  cmp  al, '1'
  je   even_number

;IF NO JUMP, AL == '0'.  
  mov  ah, 9
  mov  dx, offset msj3
  int  21h           
  jmp  wait_for_key  ;SKIP "EVEN_NUMBER".
even_number:  
  mov  ah, 9
  mov  dx, offset msj2
  int  21h           

;WAIT FOR USER TO PRESS ANY KEY.
wait_for_key:
  mov  ah,7
  int  21h

;FINISH THE PROGRAM.
  mov  ax, 4c00h
  int  21h           

;------------------------------------------
;THIS PROCEDURE RETURNS '1' IN STACK IF THE NUMBER
;IS EVEN OR '0' IF IT'S ODD.
;ASSUME THE NUMBER COMES IN BX.

proc even_or_odd
;DIVIDE NUMBER BY 2.    
  mov  ax, bx
  mov  bl, 2
  div  bl  ;AX / BL (NUMBER / 2). RESULT : QUOTIENT=AL, REMAINDER=AH.

;IF REMAINDER IS 0 THEN NUMBER IS EVEN, ELSE IT'S ODD.
  cmp  ah, 0
  je   its_even

;IF NO JUMP, IT'S ODD.
  mov  ax, '0'  ;VALUE TO STORE IN STACK.
  jmp  finish   ;SKIP "ITS_EVEN".
its_even:  
  mov  ax, '1'  ;VALUE TO STORE IN STACK.
finish:

;STORE VALUE IN STACK. IMPORTANT: WHEN THIS PROCEDURE
;WAS CALLED, THE RETURN ADDRESS WAS PUSHED IN STACK. TO
;RETURN THE VALUE IN STACK IT'S NECESSARY TO RETRIEVE
;THE RETURN ADDRESS FIRST, PUSH THE VALUE ('0' OR '1')
;AND PUSH THE RETURN ADDRESS BACK.
  pop  bx  ;RETRIEVE RETURN ADDRESS FROM THE CALL.
  push ax  ;VALUE TO RETURN ('0' OR '1').
  push bx  ;PUT RETURN ADDRESS BACK.

  ret  ;THIS "RET" POPS THE RETURN ADDRESS. THIS IS HOW
endp   ;IT KNOWS HOW TO RETURN WHERE THE PROC WAS CALLED.  

;------------------------------------------
;CONVERT STRING TO NUMBER IN BX.
;SI MUST ENTER POINTING TO THE STRING.

proc string2number
;MAKE SI TO POINT TO THE LEAST SIGNIFICANT DIGIT.
  inc  si ;POINTS TO THE NUMBER OF CHARACTERS ENTERED.
  mov  cl, [ si ] ;NUMBER OF CHARACTERS ENTERED.                                         
  mov  ch, 0 ;CLEAR CH, NOW CX==CL.
  add  si, cx ;NOW SI POINTS TO LEAST SIGNIFICANT DIGIT.

;CONVERT STRING.
  mov  bx, 0
  mov  bp, 1 ;MULTIPLE OF 10 TO MULTIPLY EVERY DIGIT.
repeat:
;CONVERT CHARACTER.                    
  mov  al, [ si ] ;CHARACTER TO PROCESS.
  sub  al, 48 ;CONVERT ASCII CHARACTER TO DIGIT.
  mov  ah, 0 ;CLEAR AH, NOW AX==AL.
  mul  bp ;AX*BP = DX:AX.
  add  bx,ax ;ADD RESULT TO BX. 
;INCREASE MULTIPLE OF 10 (1, 10, 100...).
  mov  ax, bp
  mov  bp, 10
  mul  bp ;AX*10 = DX:AX.
  mov  bp, ax ;NEW MULTIPLE OF 10.  
;CHECK IF WE HAVE FINISHED.
  dec  si ;NEXT DIGIT TO PROCESS.
  loop repeat ;COUNTER CX-1, IF NOT ZERO, REPEAT.

  ret 
endp    

;------------------------------------------
proc clear_screen
  mov  ah,0
  mov  al,3
  int  10H
  ret
endp

注意变量“str”,用于从键盘捕获数字,使用3-DB格式:第一个DB指定最大长度(加上一个用于结束chr(13)),另一个DB指定长度为用户输入的字符串,以及字符串本身的第三个数据库。

杰斯特是解决问题的另一种方法。甚至还有第三种解决方案:将数字向右移位(指令SHR),丢弃的位存储在进位标志中,然后我们可以通过指令JC或JNC检查进位标志是0还是1。

答案 1 :(得分:2)

当函数返回堆栈上的值时,通常由

实现
  1. 删除调用序列中推送的所有值(堆叠 参数,帧指针和返回地址,保存的寄存器),相对于调用站点产生一个干净的堆栈,
  2. 将功能值推送到干净的堆栈上,
  3. 然后退出函数返回地址。
  4. 人们可以直接实现这些想法,或者可以以优化的方式实现这些想法。

    以下是执行此操作的典型,简单的代码:

    call_site:  call get_number  ; assumed to eax
                 push eax         ;  push argument onto the stack
                 call is_even_or_odd
                 pop  eax         ; get the function result back from the stack
                 test eax, eax
                 je   even
     odd:        ...
    
    
     is_even_or_odd:
                 push   ebp        ; save frame pointer
                 mov    eax, 8[ebp] ; get argument (above saved EBP and return address)
                 and    eax, 1      ; now eax == 0 if even, 1 if odd
    
                 pop    ebp         ; pop the push values from the stack
                 pop    edx
                 leas   4[esp]      ; pop the argument
                 push   eax         ; push the result
                 jmp    edx         ; go to the return address
    

    上述程序以一般方式编码。这个特定的例程可以更紧凑地编码,并具有更好的性能:

     is_even_or_odd:
                 ; no need to save frame pointer; just leave EBP alone
                 pop    edx         ; get return address
                 pop    eax         ; pop the argument
                 and    eax, 1      ; now eax == 0 if even, 1 if odd
                 push   eax         ; push the result
                 push   edx         ; instead of "jmp edx"
                 ret
    

    最后的特殊习惯用法是推送返回地址然后执行“ret”,这样硬件就可以准确跟踪其影子堆栈中的返回地址。这意味着当它命中ret指令时,它假定原始返回地址是值(这是它在阴影堆栈中的值)并且可以立即开始在返回点处获取指令。 “jmp edx”习语有效,但会破坏分支地址预测,减慢从子程序返回所需的时间。

    另一个变体使用调用堆栈中的空间作为参数, 返回结果。这适用于参数的大小, 等于结果的大小,如下例所示:

     is_even_or_odd:
                 ; no need to save frame pointer; just leave EBP alone
                 mov    eax, 4[esp] ; get the argument
                 and    eax, 1      ; now eax == 0 if even, 1 if odd
                 mov    4[esp], eax ; smash the argument with the result
                 ret
    

答案 2 :(得分:0)

这是一件特别愚蠢的事情,当然,这是可能的。 如果您也获得了堆栈上的输入,只需将其替换为结果即可。 假设16位代码,如下所示:

push bp
mov bp, sp
and word [bp+4], 1 ; keep lowest bit
xor byte [bp+4], 1 ; flip it to return 1 for even
pop bp
ret