# 实验报告(七):字符串操作 ## 实验目的 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汇编语言的字符串处理能力有了更深入的理解,为后续学习更复杂的系统编程打下了基础。