Assembly 在程序集中按值传递和按引用传递
我试图解决这个问题: 创建一个PROC过程,该过程将一个参数作为传递值,并根据作为参数传递的编号打印“X”的编号打印前,确保参数为正数,在程序结束时,您需要将已使用的寄存器更改回其第一个值 如果程序的输入为5,则控制台上的输出应为: XXXXX 这是我的代码:Assembly 在程序集中按值传递和按引用传递,assembly,parameters,dos,masm,x86-16,x86,Assembly,Parameters,Dos,Masm,X86 16,X86,我试图解决这个问题: 创建一个PROC过程,该过程将一个参数作为传递值,并根据作为参数传递的编号打印“X”的编号打印前,确保参数为正数,在程序结束时,您需要将已使用的寄存器更改回其第一个值 如果程序的输入为5,则控制台上的输出应为: XXXXX 这是我的代码: var db 5 ;In the dataseg push [var] ;in the codeseg proc example pop cx cmp cx,0 ja print prin
var db 5 ;In the dataseg
push [var] ;in the codeseg
proc example
pop cx
cmp cx,0
ja print
print:
mov dl, 'X'
mov ah, 2h
int 21h
loop print
ret
endp example
这段代码会按预期工作吗?如果不能,为什么?如何修复它?不,您的代码严重损坏 函数通过
mov ecx、[esp+4]
之类的东西从堆栈访问其参数。或者在16位代码中,[sp+2]
不是可编码的寻址模式,因此您应该创建一个堆栈帧:推送bp
/mov bp,sp
/mov cx,[bp+4]
。(并在函数结束时撤消此操作)。Google“stack frame”了解更多信息,以及x86函数如何访问堆栈上的参数。(或查看标记wiki)
这是假设您需要在堆栈上传递参数。一些调用约定在寄存器中传递前两个参数,这将指令保存在小函数中
您的函数以
ret
结尾,这意味着您假设您是通过调用被调用的。返回地址将位于函数条目上的[sp]
(或32位代码中的[esp]
)。此时的pop
将加载返回地址。如果在返回地址低于sp
时出现中断,它将被覆盖,因此在ret
之前再次向下调整sp
是不安全的
推一个arg然后陷入函数中不是好的做法
你的分行也错了:
cmp cx, 0
ja print
print: ; execution ends up here whether the branch is taken or not
使用ja
意味着您将arg视为未签名。这很好,因为它仍然有可能是非正的。然而,我认为这项任务的目的是让你将arg视为已签署。尝试类似于jle nonpositive
,并将nonpositive:
放在有用的地方
TagWiki有一些指向教程和参考资料的链接。有了这个+调试器+谷歌,你应该能够回答你自己的问题。作为Peter答案的补充,你可以使用MASM/TASM生成序言和尾声代码,为你设置BP,并允许你通过标签访问过程/函数参数。可以找到关于MASM和TASM使用的PROC子程序的相当好的教程
我还将var
更改为一个单词而不是一个字节。生成的代码如下所示:
.MODEL SMALL
.STACK 100H
.DATA
var dw 5 ; Change to 16-bit WORD
.CODE
example proc C ; C Calling convention - parameters on stack right to left
ARG num:WORD ; We take one argument called `num` that is a word
mov cx, num ; Move the 16-bit value in `num` to CX counter
; same as: mov cx, [bp+4]
; [bp+0] is saved copy of BP put on stack by MASM's prologue
; [bp+2] return address placed on stack by CALL
cmp cx, 0
jle negative ; If we are less than or equal to 0, exit procedure
mov dl, 'X'
mov ah, 2h ; ah and dl not destroyed by int 21h/ah=2 so set them once
; before loop
print:
int 21h ; Print an 'X'
loop print ; Continue until loop is 0
negative:
ret
endp example
main proc
mov ax, @data ; initialize DS
mov ds, ax
push [var] ; Push 2-byte value at `var` (pushing by value)
call example
add sp, 2 ; Remove parameter from stack
; Not necessary since we use int 21h to exit right after
mov ah, 4ch ; return control to DOS
int 21h
main endp
end main ; Entry point = label main
上面的代码将为过程生成这些说明示例:
example proc
push bp ; Save BP on stack \
mov bp, sp ; Set BP to SP / Function prologue
; [bp+0] is saved copy of BP put on stack by prologue
; [bp+2] return address placed on stack by CALL
; [bp+4] first parameter (NUM)
mov cx, [bp+4] ; Move the value at BP+4 (NUM) to CX counter
cmp cx, 0
jle negative ; If we are less than or equal to 0, exit procedure
mov dl, 'X'
mov ah, 2h ; ah and dl not destroyed by int 21h/ah=2 so set them once
; before loop
print:
int 21h ; Print an 'X'
loop print ; Continue until loop is 0
negative:
mov sp, bp ; Restore stack pointer \
pop bp ; Restore BP register / Function epilogue
ret
endp example
我把它作为一个练习留给读者,让他们确定示例过程中更改的所有寄存器,并按照家庭作业中的要求保存/恢复它们。提示:在ARG指令之后推送它们,并在RET
之前按相反顺序弹出它们。没有PUSH r/m8
,因此您应该使用dw
(或dd
)声明您的var
)。您还应该通过使用push word ptr[var]
(或dword ptr
)明确要推送多少字节。是否需要在堆栈上传递参数?OP使用的是16位代码。您的一些回答可能会让人困惑,因为它讨论的是32位代码。ie:[esp]
,mov ecx,[esp+4]
。OP代码中的语法也是MASM/TASM。@MichaelPetch:我不想写[sp+4]
(或[sp+2]
?),因为那不是合法有效的地址。我不想花更多的时间给出更好的答案,也不想喋喋不休地说16位是多么愚蠢。我是否编写了任何无效的MASM语法?它不使用.local\u label
s吗?至于语法,您可以在MASM/TASM标签的开头添加句点,但它不被视为local。您需要将LOCALS
指令添加到汇编文件的顶部,然后在标签前面加上@
@MichaelPetch:我从来没有说过这个问题是离题的,只是我对其中的那部分不感兴趣。不过,它急需编辑,我确实想留下一些关于弹出访问args的回复。@MichaelPetch:我更新了我的答案,因为你是对的,所以有低质量的答案是不好的。