8086汇编(16位汇编)学习笔记08.函数
### 函数结构#### 函数结构的演变
函数的结构并不是随随便便就出来的而是解决了很多问题之后,大家统一认为那个结构是最好的一种方式
例如:模拟函数实现2个数相加
##### 不用函数实现两个数相加
```
;这是栈段
stack segment stack
db 512 dup(0)
stackends
;这是数据段
data segment
data ends
;这里是代码
CODE segment
START:
assume ds:data
mov ax, data
mov ds, ax
mov si,5H
mov di,6H
add si,di
mov si,10H
mov di,6H
add si,di
mov si,0A0h
mov di,6H
add si,di
mov ax,4C00H; 带参数返回的退出
int 21h ;使用21号中断
CODE ends
end START
```
##### 模拟函数实现
```
;这是栈段
stack segment stack
db 512 dup(0)
stackends
;这是数据段
data segment
data ends
MYADD:
add si,di
mov ax,si
;这里是代码
CODE segment
START:
assume ds:data
mov ax, data
mov ds, ax
mov si,5H
mov di,6H
jmpMYADD
mov si,10H
mov di,6H
mov si,0A0h
mov di,6H
jmpMYADD
mov ax,4C00H; 带参数返回的退出
int 21h ;使用21号中断
CODE ends
end START
```
此时跳过去了,但函数无法返回,如果使用 jmp返回,那么其他地方调用就还是没法返回 而且无法重复的调用,那如何让他正确返回呢, 解决办法是 jmp 跳转的地址不固定,把它存起,要跳转的时候再把它取出来, 存的地址如果用全局变量保存,可需要的变量过多,而且不确定,最好的方法是用栈保存
###### 普通的 函数跳转与返回
```
;这是栈段
stack segment stack
db 512 dup(0)
stackends
;这是数据段
data segment
data ends
;这里是代码
CODE segment
MYADD:
add si,di
mov ax,si
;jmpMYADD
pop bx ;取出函数返回地址
jmp bx ;跳转到指定的地址
START:
assume ds:data
mov ax, data
mov ds, ax
mov si,5H
mov di,6H
mov ax, MYRET
push ax ;将返回地址入栈 ,不直接用 pushMYRET 是不能直接push立即数,需要用寄存器中转
jmpMYADD;跳转到函数
MYRET:
mov si,10H
mov di,6H
;jmpMYADD
mov ax, MYRET1
push ax ;将返回地址入栈 ,不直接用 pushMYRET1 是不能直接push立即数,需要用寄存器中转
jmpMYADD;跳转到函数
MYRET1:
mov si,0A0h
mov di,6H
jmpMYADD
mov ax,4C00H; 带参数返回的退出
int 21h ;使用21号中断
CODE ends
end START
```
!(./notesimg/1652786190740-6312175a-53aa-4b91-a123-bfd06d1ba8de.png)
!(./notesimg/1652785637873-8e0cd897-8be0-472d-ad9b-702160dcb088.png)
6汇编关键字 的 函数跳转与返回
这样每次我们跳转前都需要 将返回地址入栈 , 再进行跳转, .函数返回时 需要先将地址出栈 ,再跳转.比较麻烦,因此汇编提供了相同功能的指令 call
```
;这是栈段
stack segment stack
db 512 dup(0)
stackends
;这是数据段
data segment
data ends
;这里是代码
CODE segment
MYADD:
add si,di
mov ax,si
;jmpMYADD
;pop bx ;取出函数返回地址
;jmp bx ;跳转到指定的地址
ret ;等同于上面注释2行代码,返回地址出栈,跳转
START:
assume ds:data
mov ax, data
mov ds, ax
mov si,5H
mov di,6H
;mov ax, MYRET
;push ax ;将返回地址入栈 ,不直接用 pushMYRET 是不能直接push立即数,需要用寄存器中转
;jmpMYADD;跳转到函数
;MYRET:
callMYADD ;等能等同于上面注释的5行代码,取地址,入栈,跳转
mov si,10H
mov di,6H
;jmpMYADD
;mov ax, MYRET1
;push ax ;将返回地址入栈 ,不直接用 pushMYRET1 是不能直接push立即数,需要用寄存器中转
;jmpMYADD;跳转到函数
;MYRET1:
callMYADD ;等能等同于上面注释的5行代码,取地址,入栈,跳转
mov si,0A0h
mov di,6H
jmpMYADD
mov ax,4C00H; 带参数返回的退出
int 21h ;使用21号中断
CODE ends
end START
```
!(./notesimg/1652786150946-68e5902e-ab13-4a6e-b8b6-c573fb246287.png)
!(./notesimg/1652786604010-b83f7c63-7fba-430e-b696-6172c01daa69.png)!(./notesimg/1652786738305-19a6452c-7c9a-41c7-b1af-51e295d12649.png)
###### 参数
上面的 参数使用 si 和di传递的参数,但是如果参数多的话,寄存器可能不够使用,而且如果之前 si 和 di 保存了其他数据,我们需要先进行保存,用完之后再恢复,寄存器用的越多,那么使用发之前存的就越多,用完之后恢复的也就越多,所以传参尽量也不用寄存器 而是用栈,用的时候再到栈里面去拿出来
!(./notesimg/1652787761458-f0f8ebe5-7cff-4d18-baa0-f678488e3d95.png)
```
;这是栈段
stack segment stack
db 512 dup(0)
stackends
;这是数据段
data segment
data ends
;这里是代码
CODE segment
MYADD:
mov bp,sp
mov si, ;第二个参数
mov di, ;第一个参数
add si,di
mov ax,si
;jmpMYADD
;pop bx ;取出函数返回地址
;jmp bx ;跳转到指定的地址
ret ;等同于上面注释2行代码,返回地址出栈,跳转
START:
assume ds:data
mov ax, data
mov ds, ax
mov si,5H
mov di,6H
pushdi
pushsi
;mov ax, MYRET
;push ax ;将返回地址入栈 ,不直接用 pushMYRET 是不能直接push立即数,需要用寄存器中转
;jmpMYADD;跳转到函数
;MYRET:
callMYADD ;等能等同于上面注释的5行代码,取地址,入栈,跳转
mov si,10H
mov di,6H
;jmpMYADD
;mov ax, MYRET1
;push ax ;将返回地址入栈 ,不直接用 pushMYRET1 是不能直接push立即数,需要用寄存器中转
;jmpMYADD;跳转到函数
;MYRET1:
callMYADD ;等能等同于上面注释的5行代码,取地址,入栈,跳转
mov si,0A0h
mov di,6H
jmpMYADD
mov ax,4C00H; 带参数返回的退出
int 21h ;使用21号中断
CODE ends
end START
```
!(./notesimg/1652788100545-8a750534-0ff3-4c2e-b2de-416ba6ed24f2.png)
此是运行结果结果是正确的,但是栈顶确实错误的,没有平栈,因此需要需要平参数的栈
1. 由调用方平栈 调用函数后 add sp ,入栈参数总长度 cdecel c调用约定
2. 由被调用方平栈 ret 入栈参数总长度 stdcall 标准调用约定
###### 保存和恢复寄存器环境
当我们调用函数时,函数如果使用了我们之前保存数据的寄存器,那么函数是用之后,我们保存的数据将会丢失,因此函数在使用寄存器之前,需要先保存寄存器数据,使用之后再恢复,但是调用者并不知道函数用到了哪些寄存器,因此这件事需要函数内部来实现,但是函数自己保存寄存器环境好之后有一个问题,就是参数取值问题,解决方法是 bp,sp分离,
通过bp取参数,这样不管sp如何变化,bp的值是不变的,此时bp将无法在干其他事了,因此 bp又称为 栈帧指针
注意:ax 不需要保存,因为所有函数 ax默认当做返回值,因此任何函数都有可能修改 ax
!(./notesimg/1652789682735-de450012-ef9e-400f-966f-d7dab23fa6fe.png)
```
;这是栈段
stack segment stack
db 512 dup(0)
stackends
;这是数据段
data segment
data ends
;这里是代码
CODE segment
MYADD:
;保存寄存器环境
push bp
mov bp,sp ;sp,bp分离
push si
push di
mov bp,sp
mov si, ;第二个参数
mov di, ;第一个参数
add si,di
mov ax,si ;ax 不保存,是因为用来当返回值
;恢复寄存器环境,注意出栈和入栈顺序想反
pop di
pop si
pop bp
;jmpMYADD
;pop bx ;取出函数返回地址
;jmp bx ;跳转到指定的地址
ret 4 ;等同于上面注释2行代码,返回地址出栈,跳转,后面跟数字表示返回后再出栈四个字节(入栈参数总长度)
; stdcall 标准调用约定
START:
assume ds:data
mov ax, data
mov ds, ax
mov si,5H
mov di,6H
pushdi
pushsi
;mov ax, MYRET
;push ax ;将返回地址入栈 ,不直接用 pushMYRET 是不能直接push立即数,需要用寄存器中转
;jmpMYADD;跳转到函数
;MYRET:
call MYADD ;等能等同于上面注释的4行代码,取地址,入栈,跳转
;add sp,4 ;平参数的栈 2个参数的总长度位四字节 c调用约定平栈cdecel
mov ax,4C00H; 带参数返回的退出
int 21h ;使用21号中断
CODE ends
end START
```
!(./notesimg/1652790174813-03f8c71f-bb31-4e74-adc2-7c6f2c4e6a0c.png)
###### 局部变量
局部变量需要自己去栈上申请,一般在保存bp环境之后 ,保存其他寄存器环境之前,不放在 保存所有寄存器之后的是为了访问更加方便
!(./notesimg/1652792150973-7a52abf0-1c6a-438f-936a-716cc96a8959.png)
```
;这是栈段
stack segment stack
db 512 dup(0)
stackends
;这是数据段
data segment
data ends
;这里是代码
CODE segment
MYADD:
;保存寄存器环境
push bp
mov bp,sp ;sp,bp分离
;申请局部变量空间
sub sp,4 ;申请4字节的局部变量空间
push si
push di
;局部变量赋值
mov word ptr,1122h
mov word ptr,5566h
mov bp,sp
mov si, ;第二个参数
mov di, ;第一个参数
add si,di
mov ax,si
;恢复寄存器环境,注意出栈和入栈顺序想反
pop di
pop si
;add sp,4;释放局部空间,释放大小和申请大小必须一致
mov sp,bp;释放局部空间
pop bp
;jmpMYADD
;pop bx ;取出函数返回地址
;jmp bx ;跳转到指定的地址
ret 4 ;等同于上面注释2行代码,返回地址出栈,跳转,后面跟数字表示返回后再出栈四个字节(入栈参数总长度)
; stdcall 标准调用约定
START:
assume ds:data
mov ax, data
mov ds, ax
mov si,5H
mov di,6H
pushdi
pushsi
;mov ax, MYRET
;push ax ;将返回地址入栈 ,不直接用 pushMYRET 是不能直接push立即数,需要用寄存器中转
;jmpMYADD;跳转到函数
;MYRET:
call MYADD ;等能等同于上面注释的4行代码,取地址,入栈,跳转
;add sp,4 ;平参数的栈 2个参数的总长度位四字节 c调用约定平栈cdecel
mov ax,4C00H; 带参数返回的退出
int 21h ;使用21号中断
CODE ends
end START
```
!(./notesimg/1652792823321-7002e715-88e9-4049-9fe8-56e2f02228fe.png)
!(./notesimg/1652793370516-e708ab03-eab4-4a5a-bac5-6f665d835221.png)
!(https://cdn.nlark.com/yuque/0/2022/png/27242010/1652793576301-aeb217f6-fc3d-4707-b925-487ab7f7519b.png)
##### 函数执行流程
1. 参数入栈
2. 返回地址入栈,跳转到函数
3. 保存栈帧指针( bp)
4. 申请局部变量空间
5. 保存寄存器环境
6. 执行函数功能
7. 恢复寄存器环境
8. 释放局部变量空间
9. 恢复栈帧指针( bp)
10. 弹出返回地址,返回[平栈]
11. [平栈]
##### 函数调用相关指令
| **指令(可选)** | **说明** | **功能** |
| ------------------------------------------------ | ------------------ | ----------------------------------------------------- |
| **call (near ptr) 标号** | **段内直接调用** | **push 返回地址****jmp 标号** |
| **call Reg****call near ptr \| word ptr ** | **段内间接调用** | **push 返回地址****jmp 函数地址** |
| **call far ptr 标号****call dword ptr ** | **段间调用** | **push 返回地址****push CS 基地址****jmp 函数地址** |
| **ret (n)** | **段内返回** | **pop ip****add sp,n** |
| **retf(n)** | **段间返回** | **popip****pop cs****add sp,n** |
段间调用不仅 ip 会被修改cs 也会被修改, 此时调用函数需要用 callfar ptr函数 返回要用retf
否则返回的实在函数的 段内,并没有返回调用 的 段
!(./notesimg/1652795914602-468ab54e-36e5-46e6-85e3-4652b6def43e.png)
!(./notesimg/1652796000876-993a50c7-5c87-4d6f-9b5f-4f192722ab04.png)
如果返回是retf那么在段内 调用 还是要用 callfar ptr函数
```
;这是栈段
stack segment stack
db 512 dup(0)
stackends
;这是数据段
data segment
data ends
;这里是代码
CODE segment
MYADD:
;保存寄存器环境
push bp
mov bp,sp ;sp,bp分离
;申请局部变量空间
sub sp,4 ;申请4字节的局部变量空间
push si
push di
;局部变量赋值
mov word ptr,1122h
mov word ptr,5566h
mov bp,sp
mov si, ;第二个参数
mov di, ;第一个参数
add si,di
mov ax,si
;恢复寄存器环境,注意出栈和入栈顺序想反
pop di
pop si
add sp,4;释放局部空间,释放大小和申请大小必须一致
pop bp
;jmpMYADD
;pop bx ;取出函数返回地址
;jmp bx ;跳转到指定的地址
retf 4 ;等同于上面注释2行代码,返回地址出栈,跳转,后面跟数字表示返回后再出栈四个字节(入栈参数总长度)
; stdcall 标准调用约定
START:
assume ds:data
mov ax, data
mov ds, ax
mov si,5H
mov di,6H
pushdi
pushsi
call far ptr MYADD ;等能等同于上面注释的4行代码,取地址,入栈,跳转
;add sp,4 ;平参数的栈 2个参数的总长度位四字节 c调用约定平栈cdecel
mov si,9H
mov di,6H
call far ptr MYADD ;等能等同于上面注释的4行代码,取地址,入栈,跳转
;add sp,4 ;平参数的栈 2个参数的总长度位四字节 c调用约定平栈cdecel
mov ax,4C00H; 带参数返回的退出
int 21h ;使用21号中断
CODE ends
end START
```
### masm函数语法
#### 函数语法
中括号代表可选,即可以有,也可以没有
**函数名proc[距离]****调用约定** **[参数:word, 参数名:word..]**
```
```
**local 变量:word**
```
```
**local 变量:word**
```
```
**ret ;注意不加ret程序编译链接并不会报错,因此需要自己注意**
**函数名 endp**
#### 距离
| 距离关键字 | 说明 | 段内使用 | 段间使用 |
| ------------ | ------------------------------------------------------- | ----------------------------------------------- | -------------------------------------------- |
| near | 函数只能段内的调用 函数使用ret返回调用时ip入栈 | | |
| far | 段内段间都可使用函数使用retf 返回调用时ip 和cs 都入栈 | **ret 变为 retf****call 前自动压入cs 机器码** | **ret 变为 retf****call 自动压入基址和ip** |
#### 调用约定
**有参数就需要加调用约定,没有就不需要**
| 调用约定关键字 | 说明 | |
| ---------------- | ------------ | ----------------------------------------- |
| C | 调用方平栈 | 注意:不会做平栈安全检查 |
| stdcall | 被调方平栈 | 不需要自己平栈,call 自动生成平栈机器码 |
#### 局部变量
!(./notesimg/1652797597135-5a23bdd7-4866-4c88-880f-a0799e689954.png)
#### 示例
TestProcPROC far stdcall uses bx dx si di arg1:word
```
local btVal:byte
```
```
ret
```
TestProc ENDP
##### 参数
只能是 word
```
;这是栈段
stack segment stack
db 512 dup(0)
stackends
;这是数据段
data segment
data ends
;这里是代码
CODE segment
;description 定义函数:调用约定必须要加参数类型只能是word
MYADD PROCstdcallarg1:word,arg2:word,arg3:word
mov ax,arg1 ;使用参数1
add ax,arg2 ;使用参数2
ret ;如果是stdcall 调用约定,后面不需要跟变量长度,系统会自动处理,
;而且系统会自动根据距离判断 是 ret 还是 retf
MYADD ENDP
START:
assume ds:data
mov ax, data
mov ds, ax
mov si,5
mov si,6
;参数入栈参数入栈数量跟函数参数数量必须一样,否则会破坏栈平衡
push si
push si
push di
call MYADD ;调用函数
mov ax,4C00H; 带参数返回的退出
int 21h ;使用21号中断
CODE ends
end START
```
!(./notesimg/1652798118517-a4af3fe6-42bd-4c4d-8940-a3177ca9e7eb.png)
注意: 如果函数 定义 距离是far ,那么调用时候也要加, 但返回是可以用 ret ,系统会自动判断 使用ret 还是 retf,而且压栈数量跟参数数量必须一样,不然会破坏栈平衡,系统并不会帮你检查参数个数
##### 局部变量
```
;这是栈段
stack segment stack
db 512 dup(0)
stackends
;这是数据段
data segment
data ends
;这里是代码
CODE segment
;description 定义函数:调用约定必须要加参数类型只能是word,
MYADD PROCstdcallarg1:word,arg2:word
;定义变量必须放在最前面
localvar1: word
localvar2: byte
localary: word
;使用局部变量
mov var1,6699h ;给变量1赋值
mov var2,77h ;给变量2赋值
mov ax,ss
mov es,ax ;现在在栈段
; 给数组赋值
mov cx,10 ;数组长度给cx
lea di,ary ;获取数组首地址
mov ax,0CCCCH ;给数组元素的值
rep stosb ;串存储,给字符串赋值,基址段寄存器是es
mov ax,arg1 ;使用参数1
add ax,arg2 ;使用参数2
ret ;如果是stdcall 调用约定,后面不需要跟变量长度,系统会自动处理
MYADD ENDP
START:
assume ds:data
mov ax, data
mov ds, ax
mov si,5
mov si,6
;参数入栈
push si
push di
call MYADD ;调用函数
mov ax,4C00H; 上面2条指令合成一条
int 21h ;使用21号中断
CODE ends
end START
```
!(./notesimg/1652800463347-2808ff2e-b570-455b-a8b8-6a1626c0643e.png)
##### 寄存器
关键字uses
!(./notesimg/1652800675354-7c8103a7-1b10-496f-aff7-49fa3ab6a718.png)
!(./notesimg/1652800696131-53371b60-8439-46ae-aa63-880e6fad5830.png)
#### invoke伪指令
**invoke 函数名, 参数1, 参数2, 参数3**
##### 说明
1.会生成参数入栈代码
2.如果是c调用约定,会生成平栈代码
3.如果是局部变量取地址,需要使用addr伪指令
4.使用addr的时候,注意ax的使用
!(./notesimg/1652800856111-5b5c73b0-07b2-4671-947f-309fedc14f9c.png)
```
;这是栈段
stack segment stack
db 512 dup(0)
stackends
;这是数据段
data segment
data ends
;这里是代码
CODE segment
;description 定义函数:调用约定必须要加参数类型只能是word,
MYADD PROC far stdcalluses escxdiarg1:word,arg2:word
;定义变量必须放在最前面
localvar1: word
localvar2: byte
localary: word
mov var1,6699h ;给变量1赋值
mov var2,77h ;给变量2赋值
mov ax,ss
mov es,ax ;现在在栈段
; 给数组赋值
mov cx,10 ;数组长度给cx
lea di,ary ;获取数组首地址
mov ax,0CCCCH ;给数组元素的值
rep stosw ;串存储,给字符串赋值,基址段寄存器是es
mov ax,arg1 ;使用参数1
add ax,arg2 ;使用参数2
ret ;如果是stdcall 调用约定,后面不需要跟变量长度,系统会自动处理
MYADD ENDP
START:
assume ds:data
mov ax, data
mov ds, ax
mov si,5
mov si,6
;参数入栈
;push si
;push si
;push di
;call far ptr MYADD ;调用函数
;add sp 4 ;c调用约定的话调用方平栈
invoke MYADD,5,6 ; invoke 会检查参数个数,而且如果是C调用约定,也会自动帮你平栈
;也会自动帮你判断是 far 还是 near
mov ax,4C00H; 上面2条指令合成一条
int 21h ;使用21号中断
CODE ends
end START
```
!(./notesimg/1652801092799-865d1b8e-cf83-4b94-9dd0-6d7207b68b2a.png)
##### 关键字 addr 传局部变量的地址
**addr 用于局部变量** **只能用于****invoke传局部变量的地址,不能用于其他地方**
offset 是其他地方都可以用,例如取全局变量,取函数的段偏移
```
;堆栈
stack_seg segment stack
db 512 dup(0)
stack_seg ends
;代码
code_seg segment
;交换2个函数的值地址也是 word 类型
Swap proc stdcall uses bx si dipVal1:word, pVal2:word
;取内容
mov bx, pVal1
mov ax,
mov si, pVal2
mov di,
;交换值
mov , di
mov , ax
ret
Swap endp
;一般我们在定义局部变量时会加 @ ,非必须,是为了增加可读性
;调用交换函数,交换2个函变量的值,调用函数时,传参需要传地址
MyTest PROC stdcall arg0:word
local @nVal1:word
local @nVal2:word
mov @nVal1, 1122h
mov @nVal2, 8899h
;临时改一下段基址,因为上面函数里bx,si,di的段基址是 ds
push ds
mov ax, ss
mov ds, ax
;这种是错误的offset 是求段偏移,因此@nVal1,@nVal2 是在ss段,无法确定其段偏移
;invoke Swap, offset@nVal1, offset @nVal2
;lea ax, @nVal1
;push ax
;lea ax, @nVal2
;push ax
;call Swap
;addr 就是取 变量的地址 跟上面代码作用一样
;注意用到了 ax,如果 ax里面存储了值,到这里将会丢失
invoke Swap, addr@nVal1, addr @nVal2
pop ds
ret
MyTest ENDP
START:
mov si, 5
mov di, 6
invoke MyTest, 9
mov ax, 4c00h;
int 21h
code_seg ends
end START
```
#### 函数声明
函数名 proto 距离 调用约定 参数列表
示例
`Fun1 proto far c pAddr:word`
当我们用2个文件时,一个文件调用另一个文件就需要,函数声明和 实现
汇编中 声明文件(头文件) 以后缀 .inc 结尾,实现还是以 .asm 结尾每一个asm文件必须有一个 end,但是只有一个文件 end 后面跟 开始标号 ,程序只能有一个入口点
**主程序**
```
;包头文件
include math.inc
;这是栈段
stack segment stack
db 512 dup(0)
stackends
;这是数据段
data segment
data ends
;这里是代码
CODE segment
START:
assume ds:data
mov ax, data
mov ds, ax
mov si,5
mov si,6
invoke MYADD,5,6 ; invoke 会检查参数个数,而且如果是 C 调用约定,也会自动帮你平栈
;也会自动帮你判断是 far 还是 near
mov ax,4C00H; 上面2条指令合成一条
int 21h ;使用21号中断
CODE ends
end START
```
函数头文件,声明文件math.inc
```
;声明中给函数内部使用的是不需要的 ,例如 uses 是不能有的,参数名可有可无
;MYADD PROC far stdcalluses escxdiarg1:word,arg2:word
MYADD proto far stdcall :word,:word
```
函数实现文件 math.asm
```
MATHCODE segment
;description 定义函数:调用约定必须要加参数类型只能是word,
MYADD PROC far stdcalluses escxdiarg1:word,arg2:word
;定义变量必须放在最前面
localvar1: word
localvar2: byte
localary: word
mov var1,6699h ;给变量1赋值
mov var2,77h ;给变量2赋值
mov ax,ss
mov es,ax ;现在在栈段
; 给数组赋值
mov cx,10 ;数组长度给cx
lea di,ary ;获取数组首地址
mov ax,0CCCCH ;给数组元素的值
rep stosw ;串存储,给字符串赋值,基址段寄存器是es
mov ax,arg1 ;使用参数1
add ax,arg2 ;使用参数2
ret ;如果是stdcall 调用约定,后面不需要跟变量长度,系统会自动处理
MYADD ENDP
MATHCODE ends
end
```
运行程序
!(./notesimg/1652802390649-796c0592-32e2-4803-935c-dc093fbebc48.png)
找不到实现,因此此时编译时候要将实现文件一起编译
!(./notesimg/1652802540162-d6b33565-affd-4935-bf6f-11a946e74f43.png)
### 作业
#### 实现以下函数, 并提交测试代码
##### strcpy
```
;实现以下函数, 并提交测试代码strcpy, strcat, strstr, memcpy, memset, memcmp ,puts, putchar, getline, getchar
;堆栈
stack_seg segment stack
db 512 dup(0)
stack_seg ends
;数据段
data_seg segment
g_szSrcdb "helloworld",'$',0dh,0ah,'$'
g_szDscdb 255 dup('$');
gszEnter db 0dh,0ah,'$';
data_seg ends
;代码
code_seg segment
;实现strcpy
strcpy proc stdcall uses ds si dipSrc:word,pDsc:word
mov ax,ds ;因为是从 ES:←DS:,所以把 es设成ds
mov es,ax
mov si,pSrc
mov di,pDsc
LEABELLOOP:
;拷贝字符
movsb
cmp byte ptr ,'$'
loopnz LEABELLOOP
ret
strcpy endp
START:
assume ds:data_seg
mov ax, data_seg
mov ds, ax
invoke strcpy,offset g_szSrc,offset g_szDsc
mov ax,0
lea dx,g_szDsc ;获取字符串 g_szDsc 的首地址
;输出字符传串
mov ah,9 ; 将功能编号给ah
int 21H ;调用21号中断
mov ax,0
lea dx,gszEnter ;获取字符串 g_szDsc 的首地址
;输出字符传串
mov ah,9 ; 将功能编号给ah
int 21H ;调用21号中断
mov ax, 4c00h;
int 21h
code_seg ends
end START
```
##### strcat
```
;实现以下函数, 并提交测试代码strcpy, strcat, strstr, memcpy, memset, memcmp ,puts, putchar, getline, getchar
;堆栈
stack_seg segment stack
db 512 dup(0)
stack_seg ends
;数据段
data_seg segment
g_szSrcdb "hello world!",'$'
g_szDscdb "hello world",100 dup('$');
gszEnter db 0dh,0ah,'$';
data_seg ends
;代码
code_seg segment
;实现strlength
strlength proc stdcall uses bx si cxpSrc:word
mov si,pSrc
mov cx,255
mov bx,0
LEABELLOOP1:
LODSB
add bx,1
cmp al,'$'
loopnz LEABELLOOP1
mov ax,0
mov ax,bx
sub ax,1
ret
strlength endp
;实现strcat
strcat proc stdcall uses ds si dipSrc:word,pDsc:word
mov ax,ds ;因为是从 ES:←DS:,所以把 es设成ds
mov es,ax
mov si,pSrc
mov di,pDsc
invoke strlength,di
add di,ax
LEABELLOOP:
movsb
cmp byte ptr ,'$'
loopnz LEABELLOOP
ret
strcat endp
START:
assume ds:data_seg
mov ax, data_seg
mov ds, ax
invoke strcat,offset g_szSrc,offset g_szDsc
mov ax,0
lea dx,g_szDsc ;获取字符串 g_szDsc 的首地址
;输出字符传串
mov ah,9 ; 将功能编号给ah
int 21H ;调用21号中断
mov ax,0
lea dx,gszEnter ;获取字符串 g_szDsc 的首地址
;输出字符传串
mov ah,9 ; 将功能编号给ah
int 21H ;调用21号中断
mov ax, 4c00h;
int 21h
code_seg ends
end START
```
##### strstr
```
;strstr 字符串查找 返回值-1不是字串1是字串
MyStrstr PROC far stdcall uses si di bx cx pSrc:word,pDes:word
mov si,pSrc;0158
mov di,pDes ;00f7
;保存一下每个串的起始位置 好像也可以不保存,直接用参数?
mov bx,si
mov cx,di
;串比较
STRSTR:
cmp byte ptr,"$"
jz STRSTROK ;子串到结尾了就是比完了
cmp byte ptr,"$" ;判断是否到结尾了
jz STRSTREND ;到结尾了就退出
cmpsb
jz STRSTR ;相等跳上去继续比较后面的
add bx,1
mov si,bx;不相等从后面开始比较
mov di,cx;子串则是重新开始
jmp STRSTR
STRSTREND:
mov ax,0ffffh ;返回ffff 也就是没找到
jmp ENDRET
STRSTROK:
mov ax,1 ;因为bx里存的是si每次比较的开头
jmp ENDRET
ENDRET:
ret
MyStrstr ENDP
```
##### memcpy
```
;实现以下函数, 并提交测试代码strcpy, strcat, strstr, memcpy, memset, memcmp ,puts, putchar, getline, getchar
;堆栈
stack_seg segment stack
db 512 dup(0)
stack_seg ends
;数据段
data_seg segment
g_szSrcdb "hello world!",'$'
g_szDscdb100 dup('$');
gszEnter db 0dh,0ah,'$';
data_seg ends
;代码
code_seg segment
;实现strlength
strlength proc stdcall uses bx si cxpSrc:word
mov si,pSrc
mov cx,255
mov bx,0
LEABELLOOP1:
LODSB
add bx,1
cmp al,'$'
loopnz LEABELLOOP1
mov ax,0
mov ax,bx
sub ax,1
ret
strlength endp
;实现memcpy
memcpy proc stdcall uses ds si dipSrc:word,pDsc:word,nLength:word
mov ax,ds ;因为是从 ES:←DS:,所以把 es设成ds
mov es,ax
mov si,pSrc
mov di,pDsc
LEABELLOOP:
movsb
dec nLength
cmp nLength,0
loopnz LEABELLOOP
ret
memcpy endp
START:
assume ds:data_seg
mov ax, data_seg
mov ds, ax
invoke memcpy,offset g_szSrc,offset g_szDsc,8H
mov ax,0
lea dx,g_szDsc ;获取字符串 g_szDsc 的首地址
;输出字符传串
mov ah,9 ; 将功能编号给ah
int 21H ;调用21号中断
mov ax,0
lea dx,gszEnter ;获取字符串 g_szDsc 的首地址
;输出字符传串
mov ah,9 ; 将功能编号给ah
int 21H ;调用21号中断
mov ax, 4c00h;
int 21h
code_seg ends
end START
```
##### memset
```
;实现以下函数, 并提交测试代码strcpy, strcat, strstr, memcpy, memset, memcmp ,puts, putchar, getline, getchar
;堆栈
stack_seg segment stack
db 512 dup(0)
stack_seg ends
;数据段
data_seg segment
g_szSrcdb "hello world!",'$'
g_szDscdb100 dup('$');
gszEnter db 0dh,0ah,'$';
data_seg ends
;代码
code_seg segment
;实现strlength
strlength proc stdcall uses bx si cxpSrc:word
mov si,pSrc
mov cx,255
mov bx,0
LEABELLOOP1:
LODSB
add bx,1
cmp al,'$'
loopnz LEABELLOOP1
mov ax,0
mov ax,bx
sub ax,1
ret
strlength endp
;实现memset
memset proc stdcall uses ds si dipDsc:word,nVal:word,nLength:word
mov ax,ds ;因为是从 ES:←DS:,所以把 es设成ds
mov es,ax
mov di,pDsc
mov ax,nVal
LEABELLOOP:
STOSW
dec nLength
cmp nLength,0
loopnz LEABELLOOP
ret
memset endp
START:
assume ds:data_seg
mov ax, data_seg
mov ds, ax
invoke memset,offset g_szSrc,3030H,10H
mov ax,0
lea dx,g_szDsc ;获取字符串 g_szDsc 的首地址
;输出字符传串
mov ah,9 ; 将功能编号给ah
int 21H ;调用21号中断
mov ax,0
lea dx,gszEnter ;获取字符串 g_szDsc 的首地址
;输出字符传串
mov ah,9 ; 将功能编号给ah
int 21H ;调用21号中断
mov ax, 4c00h;
int 21h
code_seg ends
end START
```
##### memcmp
```
;实现以下函数, 并提交测试代码strcpy, strcat, strstr, memcpy, memset, memcmp ,puts, putchar, getline, getchar
;堆栈
stack_seg segment stack
db 512 dup(0)
stack_seg ends
;数据段
data_seg segment
g_szSrcdb "hello world!",'$'
g_szDscdb "hellaworld",100 dup('$');
g_szEnter db 0dh,0ah,'$';
data_seg ends
;代码
code_seg segment
;实现strlength
strlength proc stdcall uses bx si cxpSrc:word
mov si,pSrc
mov cx,255
mov bx,0
LEABELLOOP1:
LODSB
add bx,1
cmp al,'$'
loopnz LEABELLOOP1
mov ax,0
mov ax,bx
sub ax,1
ret
strlength endp
;输出字符串
inputstr proc stdcall uses dxpSrc:word
mov ax,0
mov dx,pSrc ;获取字符串 g_szDsc 的首地址
;输出字符传串
mov ah,9 ; 将功能编号给ah
int 21H ;调用21号中断
ret
inputstr endp
;实现memset
memcmp proc stdcall uses ds si di cx pSrc:word,pDsc:word,nLength:word
mov ax,ds ;因为是从 ES:←DS:,所以把 es设成ds
mov es,ax
mov di,pDsc
mov si,pSrc
mov cx,nLength
REPZCMPSB
;等于
jzLEABEL1
;大于
jaLEABEL2
;小于
jbLEABEL3
LEABEL1:
mov ax,0
jmp memcmpend
LEABEL2:
mov ax,1
jmp memcmpend
LEABEL3:
mov ax,0FFFFH
jmp memcmpend
memcmpend:
ret
memcmp endp
START:
assume ds:data_seg
mov ax, data_seg
mov ds, ax
invoke memcmp,offset g_szSrc,offset g_szDsc,10H
mov ax, 4c00h;
int 21h
code_seg ends
end START
```
##### puts
```
;实现以下函数, 并提交测试代码strcpy, strcat, strstr, memcpy, memset, memcmp ,puts, putchar, getline, getchar
;堆栈
stack_seg segment stack
db 512 dup(0)
stack_seg ends
;数据段
data_seg segment
g_szSrcdb "hello world!",'$'
g_szDscdb "hello",100 dup('$');
g_szEnter db 0dh,0ah,'$';
g_szEqual db"Result si equal",0dh,0ah,'$';
g_szGreate db "Result si greate",0dh,0ah,'$';
g_szLess db "Result si less",0dh,0ah,'$';
data_seg ends
;代码
code_seg segment
;实现strlength
strlength proc stdcall uses bx si cxpSrc:word
mov si,pSrc
mov cx,255
mov bx,0
LEABELLOOP1:
LODSB
add bx,1
cmp al,'$'
loopnz LEABELLOOP1
mov ax,0
mov ax,bx
sub ax,1
ret
strlength endp
;输出字符串
puts proc stdcall uses dxpSrc:word
mov ax,0
mov dx,pSrc
mov ah,9 ; 将功能编号给ah
int 21H ;调用21号中断
mov ax,0
lea dx,g_szEnter
;输出字符传串
mov ah,9 ; 将功能编号给ah
int 21H ;调用21号中断
;输出字符传串
mov ah,9 ; 将功能编号给ah
int 21H ;调用21号中断
ret
puts endp
START:
assume ds:data_seg
mov ax, data_seg
mov ds, ax
invoke puts,offset g_szSrc
mov ax, 4c00h;
int 21h
code_seg ends
end START
```
##### putchar
```
;实现以下函数, 并提交测试代码strcpy, strcat, strstr, memcpy, memset, memcmp ,puts, putchar, getline, getchar
;堆栈
stack_seg segment stack
db 512 dup(0)
stack_seg ends
;数据段
data_seg segment
g_szSrcdb "hello world!",'$'
g_szDscdb "hello",100 dup('$');
g_szEnter db 0dh,0ah,'$';
g_char dw 'A'
g_szInputChar db '$','$',0dh,0ah,'$'
data_seg ends
;代码
code_seg segment
;实现strlength
strlength proc stdcall uses bx si cxpSrc:word
mov si,pSrc
mov cx,255
mov bx,0
LEABELLOOP1:
LODSB
add bx,1
cmp al,'$'
loopnz LEABELLOOP1
mov ax,0
mov ax,bx
sub ax,1
ret
strlength endp
;输出字符
putchar proc stdcall uses dx sichar:word
mov ax,0
lea si, g_szInputChar
mov ax,char
mov word ptr ,ax
mov ax,0
lea dx,g_szInputChar
;输出字符传串
mov ah,9 ; 将功能编号给ah
int 21H ;调用21号中断
ret
putchar endp
START:
assume ds:data_seg
mov ax, data_seg
mov ds, ax
invoke putchar,g_char
mov ax, 4c00h;
int 21h
code_seg ends
end START
```
##### getline
```
;实现以下函数, 并提交测试代码strcpy, strcat, strstr, memcpy, memset, memcmp ,puts, putchar, getline, getchar
;堆栈
stack_seg segment stack
db 512 dup(0)
stack_seg ends
;数据段
data_seg segment
g_szInputdb255 dup('$');
g_szEnterdb0dh,0ah,'$';
data_seg ends
;代码
code_seg segment
;实现strlength
getline proc stdcall uses bx si cx ds
localnLength: word
lea bx,ds:g_szInput ;获取字符串的首地址
mov dx,bx ;把地址给 dx
;输入字符串
mov ah,0aH ; 将功能编号给ah
int 21H ;调用21号中断
lea dx,ds:g_szEnter
;输出字符传串
mov ah,9 ; 将功能编号给ah
int 21H ;调用21号中断
ret
getline endp
START:
assume ds:data_seg
mov ax, data_seg
mov ds, ax
invoke getline
mov ax,0
lea dx,g_szInput
add dx,2
;输出字符传串
mov ah,9 ; 将功能编号给ah
int 21H ;调用21号中断
mov ax, 4c00h;
int 21h
code_seg ends
end START
```
页:
[1]