8086汇编(16位汇编)学习笔记07.补课
#### 输入5行文本,统计每行文本的单词的个数,并输出。```
;输入5行文本,统计每行文本的单词的个数,并输出。
stack_seg segment stack
db 512 dup(0)
stack_seg ends
data_seg segment
g_nCntOfBytes dw 0 ;输入的字符数
g_nCntOfWords dw 0 ;输入的单词数
g_szOut db 6 dup(0) ;输出字符数量
g_szRet db 0dh, 0ah, '$';回车换行
;字符数组
g_szMap db '0', '1','2','3','4','5','6','7','8','9','a','b','c','d','e','f' ;
g_buf db 255 dup(0) ;接收输入的字符串
data_seg ends
code_seg segment
START:
assume ds:data_seg
mov ax, data_seg
mov ds, ax
xor cx, cx ;cx的值置0
;输入单词
INPUT_CINTNUE:
;lea dx, g_szRet
;mov ah, 09h
;int 21h
;输入文本
mov dx, offset g_buf
lea bx,
mov byte ptr , size g_buf
mov ah, 0ah
int 21h
;回车
lea dx, g_szRet
mov ah, 09h
int 21h
;字符总个数
lea bx,
mov al, byte ptr ;获取字符总数量
cbw ;拓展为字,高位填充0
mov g_nCntOfBytes, ax ;将字符总数量赋值给g_nCntOfBytes
;直接输入回车,单词数量为0
cmp ax,0
jzSTATIC_END
;计算单词个数
mov si, offset g_buf + 2;字符串起始位置
mov di, si
add di, g_nCntOfBytes ;字符串结束位置
;统计单词个数 如果前面是空格 后面是非空格,单词数量+ 1
SKP_SPACE:;跳过空格
cmp byte ptr, ' '
jnz STATIC_WORD ;如果不是空格,单词加1
inc si ;移动到下一个字符串
cmp si, di
jz STATIC_END ;如果到了字符串结束位置直接跳转到输出字符串
jmp SKP_SPACE
STATIC_WORD: ;跳过单词
inc g_nCntOfWords;单词个数+1
;比较单词碰到空格就继续比较下一个,直到找到下一个不是空格的 单词数量+1
SKIP_WORD:
cmp byte ptr , ' '
je SKP_SPACE ;相等执行跳转,如果是空格,就还行跳过空格操作
inc si ;如果不是空格,比较下一个字符
cmp si, di ;判断是否到了字符串结束位置
jne SKIP_WORD ;不相等执行跳转
;统计单词个数结束
STATIC_END:
;输出个数保存到字符串,小尾形式保存
;取出g_nCntOfWords的低字节低四位 值
mov bx, g_nCntOfWords
and bx, 0fh
lea bx,
mov al,
lea bx,
mov , al
push cx ;将cx的值入栈,保存cx的值,后面会改动
;取出g_nCntOfWords的低字节高四位 值
mov bx, g_nCntOfWords
mov cl, 4
shr bx, cl ;右移四位
and bx, 0fh ;取低字节的 高四位
lea bx, ;获取低四字节对应的字符
mov al,
lea bx, ;将对应的字符存入字符串
mov , al
;取出g_nCntOfWords的搞字节低四位 值
mov bx, g_nCntOfWords
mov cl, 8
shr bx, cl
and bx, 0fh
lea bx,
mov al,
lea bx,
mov , al
;取出g_nCntOfWords的高字节高四位 值
mov bx, g_nCntOfWords
mov cl, 12
shr bx, cl
and bx, 0fh
lea bx,
mov al,
lea bx,
mov , al
;在输出单词数量后面加 '$'
lea bx,
mov byte ptr , '$'
;输出单词数量
mov ah, 9
mov dx, offset g_szOut
int 21h
;回车
lea dx, g_szRet
mov ah, 09h
int 21h
;将字符串重置
;mov al,'0'
;lea di,g_buf
;mov cx,g_nCntOfBytes
;REP stosb
;将用来输出结果的字符串重置
;lea di,g_szOut
;mov cx,size g_szOut
;REP stosb
mov g_nCntOfWords, 0 ;单词个数重置为0
pop cx ;cx出栈.记录之前cx的值,循环次数
inc cx
cmp cx, 5 ; 判断是否循环了5次
jlINPUT_CINTNUE ; 如果结果小于0继续循环
mov ax, 4c00h
int 21h
code_seg ends
end START
```
#### 跳转
跳转中指令用得最多的是 :
jmp 无条件跳转
jz/je 相等,等于0
jz/jnz 不相等,不等于0
其次是:
jl 小于
jg 大于
jle 小于等于
jge 大于等于
跳转的时候其实不需要怎么关注标志位,只需要根据自己的逻辑去调即可
#### 字符串
MOVSB 类似于 memcpy 拷贝
STOSB 类似于 memset 重置
LODSB 拷贝一个字符
CMPSB 类似于 memcmp 字符串比较
SCASB 类似于 strstr 字符查找
#### 无分支实现三目运算
```
reg == x ? m :n
movax , t
sub ax , x
neg ax
sbb ax , ax
and ax , n - m
add ax, m
```
#### 段超越
改变默认的 基址 段寄存器
bx , si ,di的默认段基址 寄存器是 ds
bp 的段寄存器是 ss
```
mov ax, ;此时基址段寄存器是ds
mov ax, es:, ;此时基址段寄存器是es
mov ax, ss: ;此时基址段寄存器是ss
mov ax, ;此时基址段寄存器是ss
mov ax, ;此时基址段寄存器是ss
mov ax, ds: ;此时基址段寄存器是ds
mov ax, es: ;此时基址段寄存器是es
```
#### 寻址
mov ax , imm - 立即寻址
mov ax , -直接寻址
mov ax, bx -寄存器寻址
- bp bx si di -寄存器寻间接寻址
mov ax,
mov ax, - 寄存器相对寻址
mov ax, - 基质变址
mov ax, - 基质变址相对寻址
汇编给我们提供了灵活的访问内存方式
例如: 获取数字某个元素的值
```
lea bx, g_ary; ; g_ary 是 一个 word 的数组
mov si, imm ; g_ary 数组的 第 imm 个 元素
LOOP:
mov ax, ; 获取数组第 imm个元素的值
;其它操作
add si, 2*1 ;获取下一个元素的值,2是数组元素类型大小(字节),1是下一个字节
jmp LOOP
```
注意:汇编里面并不会自动帮我们计算 元素 类型大小,,需要我们自己告诉程序
8086寄存器功能
8个通用: ax ,cx ,dxbx bpspdibi
ip寄存器:ip
标志寄存器 : flag
4个段寄存器:cs ds ss es
我们使用寄存器的时候并没有做严格的区分
8个通用寄存器 中 能用 ax 的尽量用ax ,速度最快,其次cx,一般用于累加器(计数),用dx,bx也可以,没有严格要求 ,在内存操作中si 一般存 源操作数, di 一般存目的操作数,sp ,bp 用于栈,访问局部变量需要用到,不涉及到字符串(内存)操作的话 ax cx dx bx dibi这6个寄存器区别不是很大,想怎么用都可以 , bp ,sp 有专门用途,一般不用来存数据
ip 一般不用管,cpu自己负责,随便改动ip寄存器可能导致程序出错, ip一般只能通过跳转指令(JXX ,xx是通配符,反之以 J 开头的指令 ,如 jmp ,jz 等)去改
flag 一般也是由 cpu 负责的 ,一般 我们就改动 DF(控制串方向)和IF(中断)和TF标志位(控制位 )
IF标志位是用来屏蔽中断的,一般是开着的,但是当我们修改中断向量表(内存偏移 0 ~ 400)的时候需要暂时关闭,否则可能出现修改到一半,程序跳走的情况修改指令,可通过 CLI (关) STI(开)进行修改 ,TF 标志位 32位 汇编是才用得上,调试器里经常用,单步用的
!(./notesimg/1652707492547-6e1bbc55-8519-4a86-9192-b6c4a31c937c.png)
4个段寄存器: cs,和ss基本不用管,也不要去动 es一般是串操作时用到,其他时候基本用不到, ds是唯一一个需要我们取管理的,3环是无法操作段寄存器的,只有进内核才能修改
文件读写
```
;打开指定文件,在后面最后添加字符串,
stack_seg segment stack
db 512 dup(0)
stack_seg ends
data segment
g_szFileName db "test.asm", 0 ;要操作的文件
g_szErr db "file error", '$' ;打开文件失败提示
g_bufForW db "this is a string, are you believe?" ;要写入的字符串
g_wFileCode dw 0 ;文件代号(句柄)
g_buf db 256 dup(0) ;接收文件内容的缓冲区
data ends
code segment
START:
assume ds:data
mov ax, data
mov ds, ax
;3D打开文件DS:DX=ASCIIZ串地址 成功:AX=文件代号
;AL=0 读 =1 写 错误:AX=错误码
; 根据文件名打开文件
mov dx, offset g_szFileName
mov al, 1 ;文件属性0读 1写 ,不要用3,3目前是没用的
mov ah, 3Dh ;调用打开文件功能
int 21h
jnc OPEN_SUCC ;打开文件失败 cf = 1 代表打开文件失败,CF =0 跳转
;输出打开文件失败字符串
mov dx, offset g_szErr
mov ah, 09h
int 21h
OPEN_SUCC: ;打开文件成功
mov g_wFileCode, ax ;将文件句柄给 g_wFileCode,后面需要改动ax的值
;42移动文件指针BX=文件代号 成功:DX:AX=新文件指针位置
;CX:DX=位移量 出错:AX=错误码
;AL=移动方式
;0:从文件头绝对位移
;1:从当前位置相对移动
;2:从文件尾绝对位移
;移动文件指针到末尾
mov bx, g_wFileCode;将文件句柄给bx
xor cx, cx ;将cx置0 等于 mov cx,0
xor dx, dx ;将dx置0 等于 mov dx,0
mov al, 2
mov ah, 42h
int 21h
;40写文件或设备 DS:DX=数据缓冲区地址 写成功:
;BX=文件代号 AX=实际写入的字节数
; CX=写入的字节数 写出错:AX=错误码
;写入
lea dx, g_bufForW ;获取要写入的字符串缓冲区首地址
mov bx, g_wFileCode ;将文件句柄给bx
mov cx, offset g_wFileCode - offset g_bufForW;将g_bufForW 的长度给 cx
mov ah, 40h ;调用写文件功能
int 21h
jmp READ_SUCC ;文件写入完成后,调用关闭文件功能
;3f 读文件或设备 DS:DX=数据缓冲区地址 读成功:
; BX=文件代号 AX=实际读入的字节数
; CX=读取的字节数 AX=0 已到文件尾
; 读出错:AX=错误码
;将文件内容读取到缓冲区
mov dx, offset g_buf ;获取g_buf的首地址 = lea dx, g_buf
mov bx, g_wFileCode ;将文件句柄给 bx
mov cx, size g_buf ;要读取的字节数
mov ah, 3fh
int 21h
jnc READ_SUCC ;操作失败 cf位 =1
mov dx, offset g_szErr
mov ah, 09h
int 21h
READ_SUCC:
;3E关闭文件 BX=文件代号 失败:AX=错误码
;关闭
mov bx, g_wFileCode
mov ah, 3eh
int 21h
; 带返回码的退出程序 4C带返回码结束 AL=返回码
mov ax, 4c00h
int 21h
code ends
end START
```
标志位
四则运算( add sub mul div)是需要关注标志位的,因为要确定自己的运算结果有没有出问题,有没有溢出,进位等, 加法和减法影响除了,控制位以外的其他标志位 ,乘除法要看 具体指令,一般影响 OF和ZF
跳转,逻辑运算,移位,传送 ,串传送 这些基本不关注标志位
汇编模拟 for 循环
```
for(int i = 0; i < 65; ++i)
{
printf("hello word");
}
;初始化
INIT:
xor cx, cx
;判断
IF_CMP:
cmp cx, 65
jge IF_END
;printf "hello word"
IF_STEP:
inc cx
jmp IF_CMP
IF_END:
```
页:
[1]