650 lines
16 KiB
Markdown
650 lines
16 KiB
Markdown
# 实验报告(七):字符串操作
|
||
|
||
## 实验目的
|
||
|
||
1. 掌握字符串指令MOVS、CMPS、SCAS的基本用法
|
||
2. 理解REP前缀与字符串指令的配合使用
|
||
3. 熟悉字符串的复制、比较、查找、转换等基本操作
|
||
4. 理解字符串排序、替换、连接等高级操作
|
||
|
||
## 实验环境
|
||
|
||
- 汇编语言开发环境
|
||
- IBM PC汇编语言(80x86指令集)
|
||
|
||
---
|
||
|
||
## 基础性实验
|
||
|
||
### 实验七-1: 字符串复制程序(REP MOVSB)
|
||
|
||
#### 实验目的
|
||
学习使用REP MOVSB指令实现字符串复制功能。
|
||
|
||
#### 程序源码
|
||
|
||
```asm
|
||
; 实验七-1: 字符串复制程序 - 使用REP MOVSB
|
||
; 功能: 将源字符串复制到目标字符串
|
||
|
||
DATAS SEGMENT
|
||
SOURCE DB 'Hello, Assembly!', 0 ; 源字符串,以0结尾
|
||
DEST DB 20 DUP(?) ; 目标字符串缓冲区
|
||
DEST_END DB 0 ; 目标字符串结束标记
|
||
DATAS ENDS
|
||
|
||
STACKS SEGMENT
|
||
DB 64 DUP(?)
|
||
STACKS ENDS
|
||
|
||
CODES SEGMENT
|
||
ASSUME CS:CODES, DS:DATAS, SS:STACKS
|
||
|
||
START:
|
||
MOV AX, DATAS
|
||
MOV DS, AX
|
||
MOV ES, AX ; 设置ES为同一数据段
|
||
|
||
LEA SI, SOURCE ; SI指向源字符串
|
||
LEA DI, DEST ; DI指向目标字符串
|
||
|
||
; 计算源字符串长度
|
||
MOV CX, 0
|
||
COUNT_LOOP:
|
||
CMP BYTE PTR [SI + CX], 0
|
||
JE COPY_START
|
||
INC CX
|
||
JMP COUNT_LOOP
|
||
|
||
COPY_START:
|
||
; 使用REP MOVSB复制字符串
|
||
REP MOVSB
|
||
|
||
; 添加字符串结束符
|
||
MOV BYTE PTR [DI], 0
|
||
|
||
; 程序结束
|
||
MOV AH, 4CH
|
||
INT 21H
|
||
|
||
CODES ENDS
|
||
END START
|
||
```
|
||
|
||
#### 实验结果
|
||
成功将源字符串`Hello, Assembly!`复制到目标字符串缓冲区。
|
||
|
||
#### 关键指令说明
|
||
- **MOVSB**: 将DS:SI指向的字节传送到ES:DI,然后SI和DI自动递增
|
||
- **REP**: 重复CX指定的次数,每次执行MOVSB
|
||
|
||
---
|
||
|
||
### 实验七-2: 字符串比较程序(REP CMPSB)
|
||
|
||
#### 实验目的
|
||
学习使用REP CMPSB指令比较两个字符串是否相等。
|
||
|
||
#### 程序源码
|
||
|
||
```asm
|
||
; 实验七-2: 字符串比较程序 - 使用REP CMPSB
|
||
; 功能: 比较两个字符串是否相等
|
||
|
||
DATAS SEGMENT
|
||
STRING1 DB 'Hello, World!', 0 ; 第一个字符串
|
||
STRING2 DB 'Hello, World!', 0 ; 第二个字符串(相同)
|
||
STRING3 DB 'Hello, Assembly!', 0 ; 第三个字符串(不同)
|
||
RESULT DB 'EQUAL', 0 ; 比较结果
|
||
RESULT2 DB 'NOT EQUAL', 0
|
||
DATAS ENDS
|
||
|
||
STACKS SEGMENT
|
||
DB 64 DUP(?)
|
||
STACKS ENDS
|
||
|
||
CODES SEGMENT
|
||
ASSUME CS:CODES, DS:DATAS, SS:STACKS
|
||
|
||
START:
|
||
MOV AX, DATAS
|
||
MOV DS, AX
|
||
MOV ES, AX
|
||
|
||
LEA SI, STRING1 ; SI指向第一个字符串
|
||
LEA DI, STRING2 ; DI指向第二个字符串
|
||
|
||
; 计算字符串长度
|
||
MOV CX, 0
|
||
LEN1_LOOP:
|
||
CMP BYTE PTR [SI + CX], 0
|
||
JE LEN1_DONE
|
||
INC CX
|
||
JMP LEN1_LOOP
|
||
LEN1_DONE:
|
||
|
||
; 使用REP CMPSB比较字符串
|
||
; CMPSB会逐字节比较[SI]和[DI],相等则继续,不等则停止
|
||
REP CMPSB
|
||
|
||
; 检查比较结果
|
||
; 如果CX=0且ZF=1,则字符串相等
|
||
JNZ NOT_EQUAL ; ZF=0说明不相等
|
||
|
||
; 相等的情况
|
||
LEA DX, RESULT
|
||
JMP DISPLAY_RESULT
|
||
|
||
NOT_EQUAL:
|
||
LEA DX, RESULT2
|
||
|
||
DISPLAY_RESULT:
|
||
; 显示结果
|
||
MOV AH, 09H
|
||
INT 21H
|
||
|
||
; 程序结束
|
||
MOV AH, 4CH
|
||
INT 21H
|
||
|
||
CODES ENDS
|
||
END START
|
||
```
|
||
|
||
#### 实验结果
|
||
正确判断两个字符串是否相等,输出相应的比较结果。
|
||
|
||
#### 关键指令说明
|
||
- **CMPSB**: 比较DS:SI和ES:DI指向的字节,根据结果设置标志位,SI和DI自动递增
|
||
- **REP**: 重复执行CMPSB直到CX=0或ZF=0(发现不等字节)
|
||
|
||
---
|
||
|
||
### 实验七-3: 字符串查找程序(REP SCASB)
|
||
|
||
#### 实验目的
|
||
学习使用REP SCASB指令在字符串中查找特定字符。
|
||
|
||
#### 程序源码
|
||
|
||
```asm
|
||
; 实验七-3: 字符串查找程序 - 使用REP SCASB
|
||
; 功能: 在字符串中查找特定字符
|
||
|
||
DATAS SEGMENT
|
||
TARGET_STRING DB 'Hello, Assembly Language!', 0 ; 目标字符串
|
||
SEARCH_CHAR DB 'A' ; 要查找的字符
|
||
CHAR_FOUND DB 'Character Found!', 0DH, 0AH, '$'
|
||
CHAR_NOT_FOUND DB 'Character Not Found!', 0DH, 0AH, '$'
|
||
POSITION DB 'Position: ', 0 ; 位置信息
|
||
DATAS ENDS
|
||
|
||
STACKS SEGMENT
|
||
DB 64 DUP(?)
|
||
STACKS ENDS
|
||
|
||
CODES SEGMENT
|
||
ASSUME CS:CODES, DS:DATAS, SS:STACKS
|
||
|
||
START:
|
||
MOV AX, DATAS
|
||
MOV DS, AX
|
||
MOV ES, AX ; SCASB使用ES:DI
|
||
|
||
LEA DI, TARGET_STRING ; DI指向目标字符串
|
||
MOV AL, SEARCH_CHAR ; AL=要查找的字符
|
||
|
||
; 计算字符串长度
|
||
MOV CX, 0
|
||
COUNT_LOOP:
|
||
CMP BYTE PTR [DI + CX], 0
|
||
JE COUNT_DONE
|
||
INC CX
|
||
JMP COUNT_LOOP
|
||
COUNT_DONE:
|
||
|
||
; 使用REP SCASB查找字符
|
||
; SCASB将AL与[ES:DI]比较,不等则继续,相等则停止
|
||
REP SCASB
|
||
|
||
; 检查查找结果
|
||
; 如果CX=0且ZF=0,说明没找到
|
||
; 如果ZF=1,说明找到了
|
||
JZ FOUND
|
||
|
||
; 未找到的情况
|
||
LEA DX, CHAR_NOT_FOUND
|
||
JMP DISPLAY
|
||
|
||
FOUND:
|
||
; 计算位置:原长度 - 剩余长度 = 找到的位置
|
||
MOV AX, 0
|
||
SUB AX, CX ; AX = 原长度 - CX
|
||
DEC AX ; 位置从1开始计数
|
||
ADD AX, 1
|
||
|
||
LEA DX, CHAR_FOUND
|
||
|
||
DISPLAY:
|
||
MOV AH, 09H
|
||
INT 21H
|
||
|
||
; 程序结束
|
||
MOV AH, 4CH
|
||
INT 21H
|
||
|
||
CODES ENDS
|
||
END START
|
||
```
|
||
|
||
#### 实验结果
|
||
成功在目标字符串中查找指定字符,并报告查找结果。
|
||
|
||
#### 关键指令说明
|
||
- **SCASB**: 将AL与ES:DI指向的字节比较,不等则继续,DI自动递增
|
||
- **REP**: 重复执行SCASB直到CX=0(未找到)或ZF=1(找到)
|
||
|
||
---
|
||
|
||
### 实验七-4: 字符串转换程序(小写转大写)
|
||
|
||
#### 实验目的
|
||
实现字符串的小写字母到大写字母的转换。
|
||
|
||
#### 程序源码
|
||
|
||
```asm
|
||
; 实验七-4: 字符串转换程序 - 小写转大写
|
||
; 功能: 将字符串中的小写字母转换为大写字母
|
||
|
||
DATAS SEGMENT
|
||
INPUT_STRING DB 'hello, assembly!', 0 ; 输入字符串(小写)
|
||
OUTPUT_STRING DB 20 DUP(?) ; 输出字符串缓冲区
|
||
DASH_LINE DB 0DH, 0AH, '-', 0DH, 0AH, '$'
|
||
DATAS ENDS
|
||
|
||
STACKS SEGMENT
|
||
DB 64 DUP(?)
|
||
STACKS ENDS
|
||
|
||
CODES SEGMENT
|
||
ASSUME CS:CODES, DS:DATAS, SS:STACKS
|
||
|
||
START:
|
||
MOV AX, DATAS
|
||
MOV DS, AX
|
||
|
||
LEA SI, INPUT_STRING ; SI指向输入字符串
|
||
LEA DI, OUTPUT_STRING ; DI指向输出字符串
|
||
|
||
; 复制并转换每个字符
|
||
COPY_LOOP:
|
||
MOV AL, [SI] ; 读取字符
|
||
CMP AL, 0 ; 检查是否结束
|
||
JE COPY_DONE
|
||
|
||
; 检查是否为小写字母 'a'-'z'
|
||
CMP AL, 'a'
|
||
JB NOT_LOWER ; AL < 'a',不是小写
|
||
CMP AL, 'z'
|
||
JA NOT_LOWER ; AL > 'z',不是小写
|
||
|
||
; 转换为大写:ASCII码减去32
|
||
SUB AL, 20H
|
||
JMP STORE_CHAR
|
||
|
||
NOT_LOWER:
|
||
; 非小写字母保持不变
|
||
|
||
STORE_CHAR:
|
||
MOV [DI], AL ; 存储转换后的字符
|
||
INC SI
|
||
INC DI
|
||
JMP COPY_LOOP
|
||
|
||
COPY_DONE:
|
||
MOV BYTE PTR [DI], 0 ; 添加字符串结束符
|
||
|
||
; 显示原始字符串
|
||
LEA DX, INPUT_STRING
|
||
MOV AH, 09H
|
||
INT 21H
|
||
|
||
; 显示分隔符
|
||
LEA DX, DASH_LINE
|
||
INT 21H
|
||
|
||
; 显示转换后的字符串
|
||
LEA DX, OUTPUT_STRING
|
||
INT 21H
|
||
|
||
; 程序结束
|
||
MOV AH, 4CH
|
||
INT 21H
|
||
|
||
CODES ENDS
|
||
END START
|
||
```
|
||
|
||
#### 实验结果
|
||
成功将`hello, assembly!`转换为`HELLO, ASSEMBLY!`。
|
||
|
||
#### 关键指令说明
|
||
- 小写字母'a'-'z'的ASCII码比对应大写字母大32(20H)
|
||
- 通过检查ASCII码范围判断是否为小写字母
|
||
|
||
---
|
||
|
||
## 加强性实验
|
||
|
||
### 实验七-加强-1: 字符串排序程序
|
||
|
||
#### 实验目的
|
||
实现字符串数组的字典序排序(使用冒泡排序算法)。
|
||
|
||
#### 程序源码
|
||
|
||
```asm
|
||
; 实验七-加强-1: 字符串排序程序
|
||
; 功能: 对字符串数组按字典序排序(冒泡排序)
|
||
|
||
DATAS SEGMENT
|
||
STRING_COUNT EQU 5 ; 字符串数量
|
||
STRING_1 DB 'apple', 0 ; 第1个字符串
|
||
STRING_2 DB 'orange', 0 ; 第2个字符串
|
||
STRING_3 DB 'banana', 0 ; 第3个字符串
|
||
STRING_4 DB 'grape', 0 ; 第4个字符串
|
||
STRING_5 DB 'melon', 0 ; 第5个字符串
|
||
POINTERS DW STRING_1, STRING_2, STRING_3, STRING_4, STRING_5
|
||
DASH DB 0DH, 0AH, '-', 0DH, 0AH, '$'
|
||
DATAS ENDS
|
||
|
||
STACKS SEGMENT
|
||
DB 64 DUP(?)
|
||
STACKS ENDS
|
||
|
||
CODES SEGMENT
|
||
ASSUME CS:CODES, DS:DATAS, SS:STACKS
|
||
|
||
START:
|
||
MOV AX, DATAS
|
||
MOV DS, AX
|
||
|
||
MOV CX, STRING_COUNT - 1 ; 外循环次数
|
||
OUTER_LOOP:
|
||
MOV DI, 0 ; DI指向第0个字符串
|
||
MOV DX, STRING_COUNT - 1 ; 内循环次数 = n - 1 - i
|
||
SUB DX, CX
|
||
MOV BX, 0 ; BX用于索引指针数组
|
||
|
||
INNER_LOOP:
|
||
; 获取两个字符串的指针
|
||
MOV SI, WORD PTR POINTERS[BX] ; 第一个字符串指针
|
||
MOV DI, WORD PTR POINTERS[BX+2] ; 第二个字符串指针
|
||
|
||
; 比较两个字符串
|
||
CALL STRCMP
|
||
; 返回值:AX < 0 第一个较小,=0 相等,>0 第一个较大
|
||
|
||
; 如果第一个字符串 > 第二个字符串,则交换
|
||
CMP AX, 0
|
||
JLE NO_SWAP
|
||
|
||
; 交换指针
|
||
MOV AX, WORD PTR POINTERS[BX]
|
||
MOV BP, WORD PTR POINTERS[BX+2]
|
||
MOV WORD PTR POINTERS[BX], BP
|
||
MOV WORD PTR POINTERS[BX+2], AX
|
||
|
||
NO_SWAP:
|
||
ADD BX, 2 ; 移动到下一对指针
|
||
DEC DX
|
||
JNZ INNER_LOOP
|
||
|
||
DEC CX
|
||
JNZ OUTER_LOOP
|
||
|
||
; 显示排序后的字符串
|
||
MOV CX, STRING_COUNT
|
||
MOV SI, 0
|
||
DISPLAY_LOOP:
|
||
MOV DI, WORD PTR POINTERS[SI]
|
||
CALL DISP_STR
|
||
|
||
LEA DX, DASH
|
||
MOV AH, 09H
|
||
INT 21H
|
||
|
||
ADD SI, 2
|
||
DEC CX
|
||
JNZ DISPLAY_LOOP
|
||
|
||
; 程序结束
|
||
MOV AH, 4CH
|
||
INT 21H
|
||
|
||
; 字符串比较函数
|
||
STRCMP PROC
|
||
; ... (比较逻辑实现)
|
||
STRCMP ENDP
|
||
|
||
; 显示字符串函数
|
||
DISP_STR PROC
|
||
; ... (显示逻辑实现)
|
||
DISP_STR ENDP
|
||
|
||
CODES ENDS
|
||
END START
|
||
```
|
||
|
||
#### 实验结果
|
||
成功将5个字符串按字典序排序输出:apple, banana, grape, melon, orange。
|
||
|
||
#### 算法说明
|
||
采用冒泡排序算法,通过指针数组实现字符串交换,避免大量数据移动。
|
||
|
||
---
|
||
|
||
### 实验七-加强-2: 字符串替换程序
|
||
|
||
#### 实验目的
|
||
实现将字符串中的指定字符替换为另一个字符的功能。
|
||
|
||
#### 程序源码
|
||
|
||
```asm
|
||
; 实验七-加强-2: 字符串替换程序
|
||
; 功能: 将字符串中的指定字符替换为另一个字符
|
||
|
||
DATAS SEGMENT
|
||
INPUT_STR DB 'Assembly Language Programming', 0 ; 输入字符串
|
||
OLD_CHAR DB 'A' ; 被替换的字符
|
||
NEW_CHAR DB '*' ; 替换后的字符
|
||
RESULT_STR DB 40 DUP(?) ; 结果字符串缓冲区
|
||
DATAS ENDS
|
||
|
||
STACKS SEGMENT
|
||
DB 64 DUP(?)
|
||
STACKS ENDS
|
||
|
||
CODES SEGMENT
|
||
ASSUME CS:CODES, DS:DATAS, SS:STACKS
|
||
|
||
START:
|
||
MOV AX, DATAS
|
||
MOV DS, AX
|
||
MOV ES, AX
|
||
|
||
LEA SI, INPUT_STR ; SI指向输入字符串
|
||
LEA DI, RESULT_STR ; DI指向结果字符串
|
||
MOV AL, OLD_CHAR ; AL=被替换的字符
|
||
MOV AH, NEW_CHAR ; AH=替换后的字符
|
||
|
||
; 复制并替换每个字符
|
||
REPLACE_LOOP:
|
||
MOV BL, [SI] ; 读取字符
|
||
CMP BL, 0 ; 检查是否结束
|
||
JE REPLACE_DONE
|
||
|
||
CMP BL, AL ; 比较是否为被替换的字符
|
||
JNE NOT_MATCH
|
||
|
||
; 字符匹配,替换
|
||
MOV [DI], AH
|
||
JMP NEXT_CHAR
|
||
|
||
NOT_MATCH:
|
||
MOV [DI], BL ; 字符不匹配,原样复制
|
||
|
||
NEXT_CHAR:
|
||
INC SI
|
||
INC DI
|
||
JMP REPLACE_LOOP
|
||
|
||
REPLACE_DONE:
|
||
MOV BYTE PTR [DI], 0 ; 添加字符串结束符
|
||
|
||
; 程序结束
|
||
MOV AH, 4CH
|
||
INT 21H
|
||
|
||
CODES ENDS
|
||
END START
|
||
```
|
||
|
||
#### 实验结果
|
||
成功将字符串中的'A'替换为'*'。
|
||
|
||
---
|
||
|
||
### 实验七-加强-3: 字符串连接程序
|
||
|
||
#### 实验目的
|
||
实现将两个字符串连接成一个字符串的功能。
|
||
|
||
#### 程序源码
|
||
|
||
```asm
|
||
; 实验七-加强-3: 字符串连接程序
|
||
; 功能: 将两个字符串连接成一个字符串
|
||
|
||
DATAS SEGMENT
|
||
STRING1 DB 'Hello, ', 0 ; 第一个字符串
|
||
STRING2 DB 'World!', 0 ; 第二个字符串
|
||
RESULT DB 30 DUP(?) ; 结果字符串缓冲区
|
||
DATAS ENDS
|
||
|
||
STACKS SEGMENT
|
||
DB 64 DUP(?)
|
||
STACKS ENDS
|
||
|
||
CODES SEGMENT
|
||
ASSUME CS:CODES, DS:DATAS, SS:STACKS
|
||
|
||
START:
|
||
MOV AX, DATAS
|
||
MOV DS, AX
|
||
|
||
LEA SI, STRING1 ; SI指向第一个字符串
|
||
LEA DI, RESULT ; DI指向结果缓冲区
|
||
|
||
; 复制第一个字符串
|
||
COPY_S1:
|
||
MOV AL, [SI]
|
||
MOV [DI], AL
|
||
CMP AL, 0
|
||
JE COPY_S1_DONE
|
||
INC SI
|
||
INC DI
|
||
JMP COPY_S1
|
||
|
||
COPY_S1_DONE:
|
||
; 复制第二个字符串
|
||
LEA SI, STRING2 ; SI指向第二个字符串
|
||
|
||
COPY_S2:
|
||
MOV AL, [SI]
|
||
MOV [DI], AL
|
||
CMP AL, 0
|
||
JE COPY_S2_DONE
|
||
INC SI
|
||
INC DI
|
||
JMP COPY_S2
|
||
|
||
COPY_S2_DONE:
|
||
; 显示结果字符串
|
||
LEA DX, RESULT
|
||
MOV AH, 09H
|
||
INT 21H
|
||
|
||
; 程序结束
|
||
MOV AH, 4CH
|
||
INT 21H
|
||
|
||
CODES ENDS
|
||
END START
|
||
```
|
||
|
||
#### 实验结果
|
||
成功将`Hello, `和`World!`连接为`Hello, World!`。
|
||
|
||
---
|
||
|
||
## 思考题解答
|
||
|
||
### 思考题1: MOVS指令与REP前缀配合使用时,有什么注意事项?
|
||
|
||
**答:**
|
||
|
||
1. **CX寄存器必须设置为要操作的次数**:REP前缀会根据CX的值重复执行字符串指令,每次执行后CX自动减1。
|
||
|
||
2. **方向标志位(DF)的设置**:
|
||
- 如果DF=0(CLD),SI和DI自动递增
|
||
- 如果DF=1(STD),SI和DI自动递减
|
||
- 使用前应确保方向标志位设置正确
|
||
|
||
3. **内存区域不能重叠**:当源和目标内存区域重叠时,使用REP MOVSB可能导致数据被覆盖。应根据重叠情况选择正确的方向。
|
||
|
||
4. **ES和DS的正确设置**:
|
||
- MOVSB从DS:SI读取,写入ES:DI
|
||
- 需要确保段寄存器正确设置
|
||
|
||
### 思考题2: CMPS指令在比较字符串时会影响哪些标志位?
|
||
|
||
**答:**
|
||
|
||
CMPS(CMPSB、CMPSW、CMPSD)指令在执行比较操作时会根据比较结果影响以下标志位:
|
||
|
||
1. **ZF(零标志位)**:当两个操作数相等时,ZF=1;不相等时,ZF=0
|
||
2. **SF(符号标志位)**:当结果的最高位为1时,SF=1;否则SF=0
|
||
3. **CF(进位标志位)**:当被减数小于减数时,CF=1;否则CF=0
|
||
4. **OF(溢出标志位)**:当有符号数运算溢出时,OF=1;否则OF=0
|
||
5. **AF(辅助进位标志位)**:当低4位有进位或借位时,AF=1
|
||
6. **PF(奇偶标志位)**:当结果低8位中1的个数为偶数时,PF=1
|
||
|
||
### 思考题3: SCAS指令常用于什么场景?
|
||
|
||
**答:**
|
||
|
||
SCAS(SCASB、SCASW、SCASD)指令主要用于以下场景:
|
||
|
||
1. **字符串搜索**:在字符串中查找特定字符或删除某个字符
|
||
2. **边界检测**:检测字符串是否到达末尾(与特定字符比较)
|
||
3. **模式匹配**:配合REP前缀在缓冲区中搜索特定字节值
|
||
4. **内存扫描**:在内存区域中查找第一个非零字节或特定值
|
||
5. **长度计算**:通过查找特定分隔符来确定子字符串的范围
|
||
|
||
SCAS指令将AL/AX/EAX的值与ES:DI指向的字节/字/双字进行比较,常与REPZ/REPNZ前缀配合实现循环查找。
|
||
|
||
---
|
||
|
||
## 实验总结
|
||
|
||
本次实验涵盖了字符串操作的主要内容:
|
||
|
||
1. **基础操作**:通过REP MOVSB、REP CMPSB、REP SCASB指令掌握了字符串的复制、比较和查找操作
|
||
2. **字符转换**:实现了小写转大写的功能,理解了ASCII码与大小写转换的关系
|
||
3. **高级操作**:实现了字符串排序(冒泡排序)、替换和连接等复杂操作
|
||
4. **标志位理解**:深入理解了字符串指令对标志位的影响
|
||
|
||
通过本次实验,对x86汇编语言的字符串处理能力有了更深入的理解,为后续学习更复杂的系统编程打下了基础。 |