1. 项目概述
在汇编语言编程中,字符串比较是一个基础但重要的操作。这个实验要求我们编写一个程序,能够比较两个从键盘输入的字符串,并根据比较结果输出相应的信息。如果两个字符串完全相同,则显示"YES";如果不同,则显示第一个不同字符的位置(从1开始计数)。
这个实验看似简单,但实际上涉及了多个汇编编程的核心概念:
- DOS功能调用的使用(INT 21h)
- 字符串的输入和存储
- 字符的逐个比较
- 结果的判断和输出
- 数字到ASCII码的转换
2. 环境准备与基础知识
2.1 所需工具与环境
要完成这个实验,你需要以下环境:
- MASM(Microsoft Macro Assembler):这是微软提供的汇编器,可以将汇编代码编译成可执行文件。
- DOSBox:一个x86架构的DOS模拟器,可以在现代操作系统上运行DOS程序。
- 文本编辑器:如Notepad++、VS Code等,用于编写汇编代码。
提示:如果你是Windows用户,可以直接下载MASM和DOSBox的安装包。Linux用户可以通过Wine来运行这些工具。
2.2 相关DOS功能调用
在这个实验中,我们主要使用了两个DOS功能调用:
-
09H功能:显示字符串
- 使用方法:
MOV AH, 09H LEA DX, 字符串地址INT 21H
- 使用方法:
-
0AH功能:缓冲输入
- 使用方法:
MOV AH, 0AH LEA DX, 缓冲区地址INT 21H
- 使用方法:
2.3 缓冲区结构
在DOS的0AH功能调用中,缓冲区需要特定的结构:
- 第一个字节:缓冲区最大长度
- 第二个字节:实际输入的字符数(由DOS填充)
- 从第三个字节开始:实际输入的字符
例如:
assembly复制str1 db 50,?,50 dup(?)
这里定义了一个最大50字节的缓冲区,DOS会将实际输入的字符数填充到第二个字节位置。
3. 程序设计与实现
3.1 数据段定义
程序的数据段定义了所有需要的变量和消息字符串:
assembly复制data segment
str1 db 50,?,50 dup(?) ; 第一个字符串缓冲区
str2 db 50,?,50 dup(?) ; 第二个字符串缓冲区
msg1 db 'Enter first string: $' ; 提示输入第一个字符串
msg2 db 0dh,0ah,'Enter second string: $' ; 提示输入第二个字符串
msg_yes db 0dh,0ah,'YES$' ; 字符串相同时的输出
msg_diff db 0dh,0ah,'Different at: $' ; 字符串不同时的输出前缀
data ends
注意:
0dh,0ah是回车和换行的ASCII码,用于在输出时换行。$是DOS字符串的结束符。
3.2 代码段实现
3.2.1 主程序流程
assembly复制code segment
assume cs:code,ds:data
start:
; 初始化数据段
mov ax,data
mov ds,ax
; 显示提示并输入第一个字符串
mov ah,09h
lea dx,msg1
int 21h
mov ah,0ah
lea dx,str1
int 21h
; 显示提示并输入第二个字符串
mov ah,09h
lea dx,msg2
int 21h
mov ah,0ah
lea dx,str2
int 21h
; 比较两个字符串的长度
mov al,str1+1 ; 获取第一个字符串的实际长度
cmp al,str2+1 ; 与第二个字符串的长度比较
jne diff ; 如果长度不同,直接跳转到diff
; 长度相同,逐个字符比较
mov cl,str1+1 ; 将字符串长度存入CX
mov ch,0
mov si,2 ; 从缓冲区的第三个字节开始比较
cmp_loop:
mov al,str1[si] ; 取第一个字符串的字符
cmp al,str2[si] ; 与第二个字符串的对应字符比较
jne diff ; 如果不同,跳转到diff
inc si ; 移动到下一个字符
loop cmp_loop ; 循环比较
; 如果所有字符都相同,输出"YES"
mov ah,09h
lea dx,msg_yes
int 21h
jmp exit
diff:
; 计算不同字符的位置(从1开始)
mov ax,si
sub ax,1 ; 因为SI从2开始,所以需要减去1
; 输出不同位置的提示
mov ah,09h
lea dx,msg_diff
int 21h
; 将位置数字转换为ASCII并输出
mov dl,al
aam ; ASCII调整指令
add ax,3030h ; 转换为ASCII码
cmp ah,30h ; 检查十位数是否为0
je print_unit ; 如果十位为0,只输出个位
; 输出十位数
mov dl,ah
mov ah,02h
int 21h
print_unit:
; 输出个位数
mov dl,al
mov ah,02h
int 21h
exit:
; 程序结束
mov ah,4ch
int 21h
code ends
end start
3.2.2 关键点解析
-
字符串输入:
- 使用DOS的0AH功能调用输入字符串
- 缓冲区结构必须正确设置
-
长度比较:
- 首先比较两个字符串的长度,如果长度不同,直接判定为不同
- 这样可以提高效率,避免不必要的字符比较
-
字符比较循环:
- 使用SI寄存器作为索引
- 使用LOOP指令控制循环次数
- 一旦发现不同字符,立即跳出循环
-
数字输出:
- 使用AAM指令将二进制数转换为非压缩BCD码
- 加上3030H转换为ASCII码
- 判断是否需要输出十位数(当数字大于9时)
4. 程序调试与优化
4.1 常见问题与解决方法
-
字符串输入不完整:
- 问题:输入的字符串没有被完整存储
- 原因:缓冲区大小设置不当
- 解决:确保缓冲区大小足够容纳输入字符串
-
比较结果不正确:
- 问题:程序总是输出"YES",即使字符串不同
- 原因:可能忘记比较字符串长度
- 解决:确保在字符比较前先比较字符串长度
-
位置输出错误:
- 问题:输出的位置数字不正确
- 原因:位置计算错误
- 解决:检查SI寄存器的初始值和减法操作
4.2 程序优化建议
-
添加字符串长度检查:
- 可以在输入后立即显示字符串长度,方便调试
-
改进用户界面:
- 添加更多的提示信息,使程序更友好
-
错误处理:
- 添加对输入字符串为空的情况处理
-
性能优化:
- 可以使用REP CMPSB指令代替手动循环比较
5. 扩展思考
5.1 不区分大小写的比较
如果要实现不区分大小写的字符串比较,可以在比较前将字符统一转换为大写或小写:
assembly复制cmp_loop:
mov al,str1[si]
; 转换为大写
cmp al,'a'
jb not_lower1
cmp al,'z'
ja not_lower1
sub al,32 ; 小写转大写
not_lower1:
mov bl,str2[si]
; 转换为大写
cmp bl,'a'
jb not_lower2
cmp bl,'z'
ja not_lower2
sub bl,32 ; 小写转大写
not_lower2:
cmp al,bl
jne diff
inc si
loop cmp_loop
5.2 比较子字符串
如果需要比较字符串的一部分,可以修改循环的起始和结束条件:
assembly复制; 比较从位置START开始,长度为LEN的子字符串
mov cx,LEN ; 设置比较长度
mov si,START ; 设置起始位置
add si,2 ; 跳过缓冲区头
5.3 使用字符串指令
汇编语言提供了专门的字符串操作指令,可以简化代码:
assembly复制lea si,str1+2 ; 第一个字符串的起始地址
lea di,str2+2 ; 第二个字符串的起始地址
mov cx,str1+1 ; 字符串长度
cld ; 清除方向标志(正向比较)
repe cmpsb ; 重复比较直到不同或CX=0
jz strings_equal ; 如果ZF=1,字符串相同
; 否则,SI-1就是不同字符的位置
6. 实验心得
在实际编写和调试这个程序的过程中,我总结了以下几点经验:
-
缓冲��管理:
- DOS的0AH功能调用的缓冲区结构很容易出错,务必确保第一个字节是最大长度,第二个字节留给DOS填充实际长度。
-
位置计算:
- 字符串比较时,位置计算要特别注意偏移量。因为缓冲区前两个字节不是字符串内容,所以实际字符从偏移2开始。
-
数字输出:
- 汇编语言中没有直接的"打印数字"功能,需要手动将数字转换为ASCII码。AAM指令在这个转换过程中非常有用。
-
调试技巧:
- 可以在关键位置插入临时的字符输出,帮助定位问题。例如,在循环中输出当前比较的字符和位置。
-
性能考虑:
- 在比较前先检查字符串长度可以显著提高效率,特别是当字符串长度差异很大时。
这个实验虽然基础,但涵盖了汇编语言编程的多个重要概念,包括输入输出、字符串处理、循环控制和条件判断等。通过这个练习,我对汇编语言的底层操作有了更深入的理解。
