Assembly 通过堆栈从过程返回一个值
我正在学习汇编,我必须编写一个程序(函数),它获取一个数字,如果是偶数,则返回Assembly 通过堆栈从过程返回一个值,assembly,stack,procedure,Assembly,Stack,Procedure,我正在学习汇编,我必须编写一个程序(函数),它获取一个数字,如果是偶数,则返回1,如果不是,则返回0 我必须返回答案,而不是通过寄存器或标志,而是通过堆栈(例如,我不能将答案放入bx或ax并在主程序中检查它们的值)。我该怎么做呢?这是一件特别愚蠢的事情,但这当然是可能的。 如果您也在堆栈上获得了输入,只需将其替换为结果即可。 假设是16位代码,类似这样: push bp mov bp, sp and word [bp+4], 1 ; keep lowest bit xor byte [bp+4]
1
,如果不是,则返回0
我必须返回答案,而不是通过寄存器或标志,而是通过堆栈(例如,我不能将答案放入
bx
或ax
并在主程序中检查它们的值)。我该怎么做呢?这是一件特别愚蠢的事情,但这当然是可能的。
如果您也在堆栈上获得了输入,只需将其替换为结果即可。
假设是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
下一个程序是用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表示用户输入的字符串长度,第三个DB表示字符串本身
杰斯特是解决这个问题的另一个办法。甚至还有第三种解决方案:将数字向右移动(指令SHR),删除的位存储在进位标志中,然后我们可以用指令JC或JNC检查进位标志是0还是1。当函数在堆栈上返回值时,通常由
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指令时,它假定原始返回地址是value(它在卷影堆栈中的值),并且可以立即在返回点开始获取指令。“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
答案在很大程度上取决于所使用的调用约定,了解这一点是件好事。以这种方式使用堆栈的函数有一个非常特殊的调用约定,因此编写函数和调用方时应该格外小心。在堆栈上返回值有什么傻的?高级语言编译器返回堆栈上的结构。至少,他们过去是这样。虽然在生产程序中,在堆栈上返回寄存器大小的值可能很愚蠢,但这样做的技术与返回任意大的结构的技术相同。这是一个很好的学习练习。我知道的约定使用适用的方法传递一个隐藏的输入参数,该方法指向返回值的内存区域。值本身不必在堆栈上。弄乱调用方的堆栈帧不是一个好主意,特别是如果调用方释放了参数,并且堆栈上有一个返回地址(例如x86 stdcall)。如果我通过堆栈向函数发送参数,当我在函数的中写入“ret”时,值不是变成了“垃圾”吗?如果我通过堆栈向函数发送参数,通常我必须将“ret”加上参数ident*2的数字置为白色。如果我想返回一个值,我是否应该将“pop”写入参数并弹出地址,而不是按下答案和地址?当我写“ret+number”时会发生什么?参数是否会从堆栈中删除,而不会从anwer中删除?如果我不将pop写入函数中的参数,只写入地址,并按下答案和地址,我是否应该写入“ret+num”?最好不要弄乱“ret”。我个人不喜欢做像“ret+num”这样的事情。您可以根据需要向堆栈中添加任意多的值,只需小心弹出并从“调用”返回返回地址即可。只要你注意回邮地址,一切都会好的。这些值不会是“垃圾”,因为返回地址在它们上面,这就是为什么返回地址是我们在堆栈上推的最后一个东西,所以“ret”会首先弹出它。如果我有例如3个参数+答案+地址,我写“ret 6”,anwer+2参数会被删除还是3个参数会被删除?我的意思是,这个堆栈包含5个东西。当我写ret 3时,哪一个会被删除?地址下面的3个还是堆栈底部的3个?再次-谢谢!太清楚了!你是如此耐心地回答!