3.8 串操作指令
基础知识
源串存放在数据段 (DS):
- 用SI提供源串的偏移地址。****DS:SI为寻址源串
目的串存放在附加段 (ES):
- 用DI提供目的串的偏移地址。****ES:DI为寻址目的串
方向标志 (DF):
- DF=0:SI、DI寄存器自动增加(加1用于字节串,加2用于字串),实现从低地址到高地址的操作。
- DF=1:SI、DI寄存器自动减少(减1用于字节串,减2用于字串),实现从高地址到低地址的操作。
重复前缀 (REP/REPE/REPNE):
REP:****无条件重复
- CX寄存器存储重复次数,每执行一次串操作后,CX减1,直到CX为0。
REPE/REPZ ****当相等/为零时重复串操作
REPNE/REPNZ 当不相等/不为零时重复串操作
执行步骤如下:
①先判断CX的内容,如(CX)=0或ZF=1,则串操作停止,否则执行第②步;
②(CX)-1→CX ;
③执行其后的串操作指令,转第①步。
数据字节串/字串传送指令 (MOVSB/MOVSW)
<font style="color:#DF2A3F;">MOVSB</font>
(Move String Byte): 逐字节移动数据。<font style="color:#DF2A3F;">MOVSW</font>
(Move String Word): 逐字移动数据(16位)。- 功能:将源段 DS:SI 指向的字节或字串传送到目标段 ES:DI,同时根据方向标志 DF 修改 SI 和 DI 的值,指向下一个字节或字。
示例:将内存单元首地址3100H起的100个字节传送到首地址2800H的内存单元
代码语言:javascript复制CLD ; 清除方向标志DF,使传送方向为从低地址到高地址
MOV SI, 3100H ; 将源串的起始地址3100H加载到SI
MOV DI, 2800H ; 将目的串的起始地址2800H加载到DI
MOV CX, 100 ; 将传送字节数100加载到CX
REP MOVSB ; 重复执行MOVSB指令,传送100个字节
这段代码用于将内存中从地址3100H开始的100个字节复制到从地址2800H开始的位置。以下是每个步骤的详细解析,包括数据移动的方式:
- CLD (Clear Direction Flag):
- 清除方向标志DF,使其等于0。方向标志DF决定了字符串操作指令(如MOVSB)在每次操作后地址指针是增加还是减少。DF = 0 表示指针会递增,也就是说数据从低地址向高地址传输。
- MOV SI, 3100H:
- 将源数据的起始地址3100H加载到SI寄存器。SI(Source Index)用于指向源字符串的当前字节地址。
- MOV DI, 2800H:
- 将目标数据的起始地址2800H加载到DI寄存器。DI(Destination Index)用于指向目标字符串的当前字节地址。
- MOV CX, 100:
- 将传送的字节数100加载到CX寄存器。CX寄存器作为计数器,用来指示需要传输的字节数量。在每次传输后,CX会自动减1,直到CX减为0,表示传输完成。
- REP MOVSB:
- REP指令前缀与MOVSB(Move String Byte)指令结合使用。MOVSB指令从SI指向的源地址复制一个字节到DI指向的目标地址,然后SI和DI各自递增(因为DF = 0)。REP指令会重复执行MOVSB指令,直到CX寄存器的值减为0为止。
数据传输过程:
- 第一次传输:
- SI = 3100H,DI = 2800H。
- 从地址3100H读取一个字节,存放到地址2800H。
- SI递增为3101H,DI递增为2801H,CX减1。
- 第二次传输:
- SI = 3101H,DI = 2801H。
- 从地址3101H读取一个字节,存放到地址2801H。
- SI递增为3102H,DI递增为2802H,CX再减1。
- 重复上述过程:
- 这个过程会持续进行,直到传输了100个字节(CX = 0)。最后一次传输时:
- SI = 3199H,DI = 2899H。
- 从地址3199H读取一个字节,存放到地址2899H。
- SI递增为319AH,DI递增为289AH,CX减为0,传输结束。
最终,地址3100H到3199H的100个字节的数据被完整复制到了地址2800H到2899H的位置。
数据字节串/字串比较指令 (CMPSB/CMPSW)
- 功能:比较源段 DS:SI 和目标段 ES:DI 指向的字节或字串,不返回结果,仅根据比较结果修改标志位,同时根据方向标志 DF 修改 SI 和 DI 的值,指向下一个字节或字。
示例:检查内存单元首地址2200H起的50个字节与首地址3200H起的50个字节是否相等
代码语言:javascript复制CLD ; 清除方向标志DF,使比较方向为从低地址到高地址
MOV SI, 2200H ; 将源串的起始地址2200H加载到SI
MOV DI, 3200H ; 将目的串的起始地址3200H加载到DI
MOV CX, 50 ; 将比较字节数50加载到CX
REPE CMPSB ; 如果相等且ZF=1,继续比较,直到CX=0或发现不相等
JZ LP1 ; 如果ZF=1,则跳转到标签LP1,表示所有字节都相等
DEC SI ; 如果不相等,将SI指针减1,指向第一个不相等的字节
MOV BX, SI ; 将第一个不相等字节的地址存入BX
MOV AL, [SI] ; 将源串中第一个不相等的字节内容存入AL
JMP LP2 ; 跳转到标签LP2
LP1: MOV BX, 0 ; 如果所有字节相等,则将BX置为0
LP2: ; 继续执行后续代码
数据字节串/字串检索指令(SCASB/SCASW)
功能:
SCASB
(Scan String Byte):逐字节比较<font style="color:#DF2A3F;">AL</font>**
寄存器中的值与[ES:DI]
段内存中的值。SCASW
(Scan String Word):逐字比较<font style="color:#DF2A3F;">AX</font>**
寄存器中的值与[ES:DI]
段内存中的值。
关键点:
- 比较的结果不会返回到寄存器中,而是影响标志位,如
ZF
(Zero Flag),CF
(Carry Flag)等。 - 在比较完成后,
DI
指针会自动递增(或递减)指向下一个字节(字),这取决于CLD
或STD
指令设置的方向标志。
示例: 在内存附加段(ES
段)4300H地址开始的100个字节中查找字符 *
。如果找到,偏移地址存入BX
寄存器;如果未找到,BX
= 0。
代码语言:javascript复制CLD ; 清除方向标志,设置为向前搜索
MOV DI, 4300H ; 初始化DI为4300H,指向内存中的第一个字节
MOV AL, '*' ; 在AL中加载要查找的字符 '*'
MOV CX, 100 ; 设置搜索范围为100个字节
REPNZ SCASB ; 逐字节比较,直到找到'*' 或 CX=0
JNZ LP1 ; 如果未找到(ZF=0),跳转到LP1
DEC DI ; 减小 DI,因为 SCASB 会多移动一个字节
MOV BX, DI ; 将找到的位置偏移地址存入BX
JMP LP2 ; 跳转到 LP2
LP1:
MOV BX, 0 ; 未找到 '*',将 BX 设为 0
LP2:
; 继续其他代码
总结(CMPSB/CMPSW)和(SCASB/SCASW)的区别
CMPSB/CMPSW
:- 比较操作: 两个字符串的内容逐字节或逐字进行比较。
- 应用场景: 用于比较两个字符串,通常用于验证它们是否相等或查找不同之处。
SCASB/SCASW
:- 检索操作: 搜索单个字符串中的特定值。
- 应用场景: 用于在字符串中查找特定的字节或字,并根据标志位确定是否找到了目标值。
简而言之,CMPSB/CMPSW
是比较两个字符串中的数据,而 SCASB/SCASW
是在一个字符串中搜索特定的数据。
只能操纵指定的寄存器
这些字符串操作指令在 x86 汇编中只能操纵特定的寄存器,这是指令设计的一个特点。具体来说:
- CMPSB/CMPSW: 源寄存器: SI(Source Index),指向源字符串的内存地址,使用DS(Data Segment)段。 目标寄存器: DI(Destination Index),指向目标字符串的内存地址,使用ES(Extra Segment)段。 比较寄存器:比较操作隐式地使用 AL 或 AX 寄存器(实际比较操作由 CMPSB 或 CMPSW 执行的内存位置与内存位置之间进行)。
- SCASB/SCASW:
目标寄存器: DI,指向字符串的内存地址,使用 ES 段。
比较寄存器: AL(对于 SCASB)或 AX(对于 SCASW)是隐式指定的寄存器。指令将 AL 或 AX 中的值与 ES:DI 指向的内存内容进行比较。
CMPSB/CMPSW
和SCASB/SCASW
指令只能操纵指定的寄存器,即SI
、DI
、AL
和AX
。这些寄存器在指令中是隐式使用的,不能被其他寄存器替代。
CMPSB/CMPSW
和 SCASB/SCASW
指令只能操纵指定的寄存器,即 SI
、DI
、AL
和 AX
。这些寄存器在指令中是隐式使用的,不能被其他寄存器替代。
数据字节串/字串读出指令(LODSB/LODSW)
功能:
LODSB
(Load String Byte):将[DS:SI]
的字节数据加载到AL
寄存器中,并将SI
指针指向下一个字节。LODSW
(Load String Word):将[DS:SI]
的字数据加载到AX
寄存器中,并将SI
指针指向下一个字(16位)。
示例 :使用 LODSB
读取数据
代码语言:javascript复制CLD ; 清除方向标志DF,使操作从低地址到高地址
MOV SI, 2200H ; 将源串的起始地址2200H加载到SI
MOV CX, 10 ; 准备读取10个字节的数据
READ_LOOP:
LODSB ; 将[DS:SI]中的字节数据加载到AL寄存器中,并将SI指针加1
; 这里可以对AL中的数据进行处理,例如存入其他寄存器或执行运算
DEC CX ; 将CX减1
JNZ READ_LOOP ; 如果CX不为0,继续循环
; 循环结束后,AL中保存了最后一个读取的字节数据,SI指向最后一个字节之后的地址
数据和指针的移动
- 数据存入
AL
:每次LODSB
指令执行时,当前SI
地址的字节数据被加载到AL
寄存器中。处理完后,AL
寄存器中存储了当前读取的数据。 SI
寄存器的移动**:每次执行LODSB
后,SI
的值增加 1。假设初始值为2200H
,经过一次LODSB
操作后,SI
会变为2201H
,然后是2202H
,如此继续直到读取完 10 个字节。因此,SI
最终会指向最后一个字节之后的位置,即2200H 10 = 220AH
。- 最终状态:
AL
:保存了最后读取的字节数据,即第 10 个字节。SI
:指向最后一个读取字节的下一个地址,即220AH
。
总结
LODSB
通过从当前SI
指向的地址读取一个字节数据到AL
寄存器,并将SI
自动增加 1,从而实现从低地址到高地址的遍历。- 经过 10 次
LODSB
操作后,SI
指向的是最后一个读取字节之后的位置(220AH
),AL
中则保存了最后一个读取的字节数据。
数据字节串/字串写入指令(STOSB/STOSW)
功能:
STOSB
(Store String Byte):将AL
寄存器的内容存入[ES:DI]
,并将DI
指针指向下一个字节。STOSW
(Store String Word):将AX
寄存器的内容存入[ES:DI]
,并将DI
指针指向下一个字(16位)。
示例 :使用 STOSB
写入数据
代码语言:javascript复制CLD ; 清除方向标志DF,使操作从低地址到高地址
MOV DI, 3200H ; 将目的串的起始地址3200H加载到DI
MOV CX, 10 ; 准备写入10个字节的数据
MOV AL, 42H ; 将字节42H加载到AL(假设需要写入的值是42H)
WRITE_LOOP:
STOSB ; 将AL寄存器中的值存入[ES:DI],并将DI指针加1
DEC CX ; 将CX减1
JNZ WRITE_LOOP ; 如果CX不为0,继续循环
; 循环结束后,内存地址[3200H]到[3209H]中存储的都是42H,DI指向下一个位置320AH