前言
本文代码一部分为完整可运行代码,另一部分仅为片段不可运行。
仅为学习过程笔记,所以并非全部完整。
每个示例后面都会说明代码是否完整,供运行参考
示例一
内存存储示例-不完整程序-不可运行
DATA SEGMENT ;定义数据段区域
NAMES DB 'TOM..', 20 ;DB(Define Byte):定义的变量为字节型 指向的每一个操作数占1个字节单元
DB 'CATE', 25
DATA ENDS
以上数据区在内存中是如何存储的?
解释 | 变量在内存中的存储(16进制表示) | 每个字符在内存中以ASCII码表示,占用1B |
NAMES开始 | 54 | 'T' |
4F | 'O' | |
4D | 'M' | |
2E | '.' | |
2E | '.' | |
14 | 20(十进制) | |
无间隙 | 43 | 'C' |
41 | 'A' | |
54 | 'T' | |
45 | 'E' | |
19 | 25(十进制) |
示例二
字符串输出示例-完整程序-可运行
DATA SEGMENT ;定义数据段区域
A DB '123ABC' ;定义变量 A 的值为123ABC 定义字符串必须用DB伪指令
DATA ENDS ;DB(Define Byte):定义的变量为字节型 指向的每一个操作数占1个字节单元
CODE SEGMENT ;定义数据段区域
ASSUME CS:CODE,DS:DATA ;说明所定义逻辑段(DATA、CODE)的性质。设定段寄存器(CS、DS)为指令助记符
START:MOV AX,DATA ;除代码段之外的其他段寄存器的初始化 设置段基地址 代码段会自己初始化
MOV DS,AX
LEA BX,A ;把变量A的偏移地址的值送给了BX
MOV CX,6
LP:MOV AH,2 ;功能号传送,功能号为2,单字符显示输出功能 出口参数:屏幕 入口参数:DL的值
MOV AL,[BX] ;由[BX]的值可得第一次传送即为A+0地址的内容,也就是字符1 (ASCII码31H)
XCHG AL,DL
INC BX
INT 21H ;系统功能调用的DOS软中断指令(大功能包,包含多个子功能)调用,中断类型码固定为21H
LOOP LP ;LOOP循环的次数和CX有关 这里的LOOP自动减一并判断是否为0
MOV AH,4CH
INT 21H ;程序执行完该2条语句后能正常返回OS 常位于程序结尾处
CODE ENDS
END START
解释 | 存储内容 | 地址 |
A开始 | 1 | 0000H |
2 | 0001H | |
3 | 0002H | |
'A' | 0003H | |
'B' | 0004H | |
'C' | 0005H |
示例三
段间间接转移示例-不完整程序-不可运行
DATA SEGMENT ;定义数据段区域
TABLE DW 3400H,5600H,2300H,4500H,2300H,1200H,2344H,3500H;定义变量 TABLE 的值
DATA ENDS ; DW (Define Word) :定义的变量为字类型 指向的每一个操作数占1个字单元
CODE SEGMENT ;定义数据段区域
ASSUME CS:CODE,DS:DATA ;说明所定义逻辑段(DATA、CODE)的性质。设定段寄存器(CS、DS)为指令助记符
START:MOV AX,DATA ;除代码段之外的其他段寄存器的初始化 设置段基地址 代码段会自己初始化
MOV DS,AX
MOV BX,OFFSET TABLE ;把TABLE的偏移地址存入BX中
MOV SI,06H
A:JMP DWORD PTR[BX+SI+2] ;段间间接转移
;后方为存储器操作数(带方括号)形式,所以为间接转移,转移的目标地址在内存中
;PTR说明了存储器操作数的字长DWORD为32位存储器操作数,也就是包含段地址和偏移地址,所以为段间转移
;综上为段间间接转移
;[BX+SI+2]基址变址相对寻址:基址寄存器内容+变址寄存器内容+位移量
;基址寄存器为BX,默认在数据段 (BP则为堆栈段)
;所以转移目标地址存放在内存数据段,偏移地址为BX+SI+2所指向的区域起始四个单元中
;因为SI=06H,所以转移目标地址就保存在TABLE+6+2=TABLE+8的字单元中
;低地址单元存放转移目标的偏移地址,高地址单元存放的是转移目标的段基地址
;TABLE+8开始的四个字节都是目标地址,回看数据段TABLE+0的字单元内容是3400H
;TABLE+2的字单元内容是5600H,TABLE+8的字单元内容是2300H (注意这里说的是字单元)
;32位计算机:1字=32位=4字节 64位计算机:1字=64位=8字节
:8086是16位处理器,1字=16位=2字节,而一字节固定等于8bit=两个十六进制位置,也就是XXH
;回到原问题,所以偏移地址就是2300H,段基地址就是1200H
;TABLE的数据存放由低到高,图示中则为由上到下
...
程序执行完标号为A 的指令后:CS=(2300H)IP=(1200H)
解释 | 存储内容 | 地址 | 解释 | 存储内容 | 地址 |
TABLE开始 | 00H | 0000H | 一字节内容 | 00H | 0008H |
34H | 0001H | 23H | 0009H | ||
先存低位后存高位 | 00H | 0002H | 一个字内容 (16位) | 00H | 000AH |
56H | 0003H | 12H | 000BH | ||
前数值在低位 后数值在高位 | 00H | 0004H | 44H | 000CH | |
23H | 0005H | 23H | 000DH | ||
00H | 0006H | 00H | 000EH | ||
45H | 0007H | 35H | 000FH |
示例四
字符串比较示例-不完整程序-不可运行
此后开始,基础的注释将减少,可以参考前面的示例。
DATA SEGMENT
STR1 DB ‘HELLO WORLD!‘
STR2 DB ‘HELLO WOOLD!’
COUNT DW 12 ;16位
FLAG DB ?
DATA ENDS
CODE SEGMENT
ASSUME CS: CODE, DS: DATA, ES: DATA ;DATA既是数据段又是附加段
START: MOV AX, DATA ;除代码段之外的其他段寄存器的初始化 设置段基地址 代码段会自己初始化
MOV DS, AX
MOV ES, AX ;同时初始化两个段
LEA BX, FLAG
LEA SI, STR1 ;源串的指针
LEA DI, STR2 ;目标串的指针
MOV CX,COUNT ;串长度值必须送给CX,因此数据段定义COUNT为DW字变量,因为要传送给CX,令字长相等
CLD ;确定了串比较的操作方向 CLD将DF清零,表示按增地址方向操作
REPE CMPSB ;串比较指令 按字节进行比较 REPE为重复前缀 CX≠0且相同则重复执行
;源串默认数据段,指针必须SI给出,目标串必须在附加段,指针由DI表示
;这里将DATA同时设置为数据段和附加段令程序变得简单了
;操作时先比较,再将SI+1、DI+1、CX-1,然后再看比较结果
;这样可以保证比了几次,数值就改变了几次
JZ NEXT1 ;通过ZF标志位是否跳转,当执行到JZ或者JE指令时,如果ZF=1则跳转,如果ZF=0,不跳转
MOV Byte PTR[BX],00H
JMP STOP
NEXT1: MOV Byte PTR(BX], OFFH
STOP:......
当程序执行到STOP时:
错误结果:SI=(9) DI=(O) CX=(3) FLAG=(00H) ZF=(0)
正确结果:SI=(9) DI=(0015H) CX=(3) FLAG=(0) ZF=(0)
SI注意这里操作的方式是先比较,再将SI+1、DI+1、CX-1,然后再看比较结果。这里结果是9,我认为写0009H应该也对,和DI对应。
DI注意是偏移地址的值,而不是存储内容。
CX注意是从12开始减。
FLAG注意是把00H写入了FLAG,应该00H和0都对。
ZF是零标志位,用来反映运算结果是否为0。如果运算结果为0,则其值为1,否则其值为0。很明显,这里不相等,结果不为0,其值为0.
解释 | 存储内容 | 地址 | 解释 | 存储内容 | 地址 |
STR1开始 | 'H' | 0000H | STR2开始 | 'H' | 000CH |
'E' | 0001H | 'E' | 000DH | ||
'L' | 0002H | 'L' | 000EH | ||
'L' | 0003H | 'L' | 000FH | ||
'O' | 0004H | 'O' | 0010H | ||
空格 | 00H | 0005H | 空格 | 00H | 0011H |
'W' | 0006H | 'W' | 0012H | ||
'O' | 0007H | 'O' | 0013H | ||
'R' | 0008H | 'O' | 0014H | ||
STR1+9 | 'L' | 0009H | STR2+9 | 'L' | 0015H |
'D' | 000AH | 'D' | 0016H | ||
此处和STR2连接 | '!' | 000BH | '!' | 0017H |
示例五
端口输入+循环示例-不完整程序-不可运行
DATA SEGMENT
SUM DB 8 DUP (0) ;[变量名] 伪指令助记符 n DUP(初值 [,初值,… ] )
;此处为 令从变量SUM地址开始设置八个字节的值,值均为0
DATA ENDS
CODE SEGMENT
ASSUME CS: CODE, DS: DATA
START: MOV AX, DATA
MOV DS, AX
LEA BX, SUM ;将SUM偏移地址写入BX,后续使用进行存储
MOV DX, 380H ;从380H端口读入数据
IN AL, DX ;输入的值为45H
MOV CX, 8
NEXT: ROR AL, 1 ;不含CF的循环右移指令 此处循环右移一位
JNC NEXT1 ;根据CF是否为1跳转,为0不跳转,为1跳转
MOV Byte PTR[BX], 0FFH ;将0FF写入SUM+自然数,其中自然数根据判断以及循环确定
JMP NEXT2 ;无条件转移指令,直接跳转NEXT2
NEXT1: MOV Byte PTR [BX], 0
NEXT2: INC BX ;逐渐变成SUM+1、SUM+2、SUM+3......
LOOP NEXT ;根据CX循环执行8次
......
如果从380H端口输入的是45H,则程序执行完后:
BX=(8)
INC八次之后BX从0变为8
AL=(45H)
循环8次之后回归原位置
SUM0-SUM7的内容为(FF,0,FF,0,0,0,FF,0)
由下表可得
解释 | AL | 循环右移 | 右移后的CF标志位 | 行为 |
45H转换二进制 | 0100 0101 | 1 | 将0FFH写入SUM | |
循环右移一次之后 | 1010 0010 | 0 | 将0写入SUM+1 | |
循环右移两次之后 | 0101 0001 | 1 | 将0FFH写入SUM+2 |
之后的循环以此类推