简介 本章介绍处理字符串和数组技术,目标是编写高效代码。 本章从 Intel 指令集中优化过的基本字符串指令开始讲述,这些指令被设计用来移动、比较、装载和存储数据块的。 Irvine32 库中的几个字符串处理过程,其实现与标准 C 字符串库的实现非常相似。
基本字符串操作指令
具体内容参考 8086 汇编相关内容的笔记。
在执行字符串操作指令前一定要注意方向标志位,因为字符串指令中隐含了 inc 和 dec 指令,如果操作不当会导致程序执行错误。
MOVSB,MOVSW 和 MOVSD
例子:复制双字数组。假设要从 source 复制 20 个整数到 target 中,在复制完数据之后,ESI 和 EDI 将指向每个数组末尾的后一个位置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 INCLUDE Irvine32.inc .data source DWORD 20 DUP(0FFFFFFFFh) target DWORD 20 DUP(?) .code main PROC cld mov ecx,LENGTHOF source mov esi,OFFSET source mov edi,OFFSET target rep movsd exit main ENDP END main
CMPSB,CMPSW 和 CMPSD 指令
例子:比较双字。假设想用 CMPSD 指令比较一对双字。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 INCLUDE Irvine32.inc .data source DWORD 1234h target DWORD 5678h .code main PROC mov esi,OFFSET source mov edi,OFFSET target cmpsd ja L1 je L2 jmp L3 L1: mov eax,1 jmp L4 L2: mov eax,0 jmp L4 L3: mov eax,-1 L4: exit main ENDP END main
如果向比较多个双字,就需要清除方向标志位,并把 ECX 初始化为计数器,然后在 CMPSD 指令前使用重复前缀:
例子:比较两个字符串 一对字符串的比较通常是从字符串头部开始的,然后按顺序逐个字符进行比较。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 ;--------------------------- ;程序名:Cmpsb.asm ;功能:使用 Cmpsb 指令比较两个字符串。 ;作者:9unk ;编写时间:2023-3-27 ;---------------------------- INCLUDE Irvine32.inc .data source BYTE "MARTIN " dest BYTE "MARTINEZ" str1 BYTE "Source is smaller",0dh,0ah,0 str2 BYTE "Source is not smaller",0dh,0ah,0 .code main PROC mov esi,OFFSET source mov edi,OFFSET dest mov cx,LENGTHOF Source repe cmpsb jb source_smaller mov edx,OFFSET str2 jmp done source_smaller: mov edx,OFFSET str1 done: call WriteString exit main ENDP END main
只有在两个字符串长度相等的条件下,使用 CMPSB 指令比较两个字符串才是可行的
SCASB,SCASW和SCASD指令 SCASB,SCASW 和 SCASD 指令把 AL/AX/EAX 中的值同由 EDI 寻址的目标内存中的字节、字或双字相比较。
扫描一个匹配字符: 在下例中,我们在字符换变量 alpha 中查找字母,如果找到该字母,EDI 指向匹配字符串后面的一个字符:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 INCLUDE Irvine32.inc .data alpha BYTE "ABCDEFGH",0 .code main PROC mov edi,OFFSET alpha mov al,'I' mov ecx,LENGTHOF alpha cld repne scasb jnz quit dec edi quit: exit main ENDP END main
STOSB,STOSW 和 STOSD 指令 STOSB,STOSW 和 STOSD 指令把 AL/AX/EAX 的内容存储在 EDI 指向的内存单元中,同时 EDI 的值根据方向标志的值增加或减少。同 REP 前缀联合使用时,这组指令在需要以指定字符填充整个字符串或数组时非常有用。例如下面的代码把 String1 的每个字节初始化为 0FFH:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 INCLUDE Irvine32.inc .data count = 100 string1 BYTE count DUP(?) .code main PROC mov al,0FFH mov edi,OFFSET string1 mov ecx,count cld rep stosb exit main ENDP END main
LODSB,LODSW 和 LODSD 指令 LODSB,LODSW 和 LODSD 指令从 ESI 的值根据方向标志位增加或减少。我们很少把 REP 前缀同 LODS 指令联用,因为装入到累加器的每个新值都会覆盖掉以前的值。相反,一般仅用 LODS 指令装入一个值。
数组乘法的例子: 下面的程序把双字数组的每一个元素都乘以一个常量,程序中使用了 LODSD 和 STOSD 指令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 ;--------------------------- ;程序名:Mult.asm ;功能:把双字数组的每一个元素都乘以一个常量。 ;作者:9unk ;编写时间:2023-3-27 ;---------------------------- INCLUDE Irvine32.inc .data array DWORD 1,2,3,4,5,6,7,8,9,10 multiplier DWORD 10 .code main PROC cld mov esi,OFFSET array mov edi,esi mov ecx,LENGTHOF array L1: lodsd mul multiplier stosd loop L1 exit main ENDP END main
精选的字符串过程 在本节中,我们介绍 Irvine32 和 Irvine16 库中几个操作以 NULL 字符结尾的字符串简单过程。
Str_compare 过程
Str_length 过程
Str_copy 过程
Str_trim 过程
Str_ucase 过程
上面的几个过程看看就行,这些功能在 8086 汇编中写过。如果看不懂,建议再照着上面写一遍。
字符串库演示程序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 ;--------------------------- ;程序名:StringDemo.asm ;功能:调用书中的库函数处理程序 ;作者:9unk ;编写时间:2023-3-27 ;---------------------------- INCLUDE Irvine32.inc .data string_1 BYTE "abcde////",0 string_2 BYTE "ABCDE",0 msg0 BYTE "string_1 in upper case: ",0 msg1 BYTE "string1 and string2 are equal",0 msg2 BYTE "string_1 is less than string_2",0 msg3 BYTE "string_1 is greater than string_2",0 msg4 BYTE "Length of string_2 is ",0 msg5 BYTE "string_1 after trimming: ",0 .code main PROC call trim_string call upper_case call compare_strings call print_length exit main ENDP trim_string PROC ;删除 string_1 上的字符 INVOKE Str_trim,ADDR string_1,'/' mov edx,OFFSET msg5 call WriteString mov edx,OFFSET string_1 call WriteString call Crlf ret trim_string ENDP upper_case PROC ;将 String_1 转换为大写 mov edx,OFFSET msg0 call WriteString INVOKE Str_ucase,ADDR string_1 mov edx,OFFSET String_1 call WriteString call Crlf ret upper_case ENDP compare_strings PROC ;比较 string_1 和 string_2 INVOKE Str_compare,ADDR string_1,ADDR string_2 .IF ZERO? mov edx,OFFSET msg1 .ELSEIF CARRY? mov edx,OFFSET msg2 .ELSE mov edx,OFFSET msg3 .ENDIF call WriteString call Crlf ret compare_strings ENDP print_length PROC ;显示 string_2 的长度 mov edx,OFFSET msg4 call WriteString INVOKE Str_length,ADDR string_2 call WriteDec call Crlf ret print_length ENDP END main
二维数组 行和列的顺序 从程序员的视角来看,多维数组就一维的数组的高阶抽象。对于二维数组在内存中行列的存储,高级语言一般采用下main两种方法:行主序和列主序。
基址变址操作数
像其他间接寻址方式一样,如果有效地址超出了程序的数据区域,就会产生通用保护异常。
计算一行之和 基址变址寻址简化了许多与二维数组相关的任务,例如计算整数矩阵一行元素的和。下面的程序计算一个8位整数的矩阵中选定行的和。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 ;--------------------------- ;程序名:RowSum.asm ;功能:计算选定行的和 ;作者:9unk ;编写时间:2023-3-28 ;---------------------------- INCLUDE Irvine32.inc .data Tables BYTE 1h ,2h ,3h,4h ,5h ,6h ,7h ,8h BYTE 9h ,10h,11h,12h,13h,14h,15h,16h BYTE 17h,18h,19h,20h,21h,22h,23h,24h line EQU 2 .code main PROC mov eax,line-1 mov ebx,OFFSET Tables mov ecx,8 call calc_row_sum exit main ENDP ;-------------------------------------------------------- calc_row_sum PROC uses ebx ecx edx esi ;入口参数:EBX = 表的偏移,EAX = 行数,ECX = 行的大小(BYTE) ;出口参数:EAX = 选定行的和 ;-------------------------------------------------------- mul ecx add ebx,eax mov eax,0 mov esi,0 L1: movzx edx,BYTE PTR[ebx+esi] add eax,edx inc esi loop L1 ret calc_row_sum ENDP END main
相对基址变址操作
整数数组的查找和排序 冒泡排序 冒泡排序法从位置0和1开始比较每对数组的值,如果每个值的顺序不正确,就进行交换。冒泡排序适合用在小数组中,但对大数组就非常低效。冒泡排序法是一个时间复杂度O(n^2)的算法,也就是说排序时间与数组元素数目(n)之间是二次方的关系。假设排序 1000 个元素需要花费0.1s,那么当数组元素的数目增加10倍的时候,排序数组需要的时间增加到原来的 10^2 倍。下表显示了不同大小数组排序所需要的时间,其中假设 1000 个数组元素可以在 0.1s 内排序完毕。
汇编语言: 一旦理解了伪码,距离用汇编语言来实现它就快了,只需要把代码写成一个过程并加上参数和局部变量就可以了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 INCLUDE Irvine32.inc BubbleSort PROTO, pArray:PTR DWOD, Count:DWORD .data array DWORD 1,3,6,8,4,9,0,2,5,7 .code main PROC INVOKE BubbleSort,ADDR array,LENGTHOF array exit main ENDP ;------------------------------------------------ BubbleSort PROC USES eax ecx esi, pArray:PTR DWOD, Count:DWORD ;32位整数数组冒泡排序 ;入口参数:数组指针,数组大小 ;出口参数:无 ;------------------------------------------------ mov ecx,Count dec ecx L1: push ecx mov esi,pArray L2: mov eax,[esi] cmp [esi+4],eax jg L3 xchg eax,[esi+4] mov [esi],eax L3: add esi,4 loop L2 pop ecx loop L1 ret BubbleSort ENDP END main
二分查找 二分查找算法在大数组中查找一个项时被证明是非常高效的,但该算法有一个重要的前提条件:数组元素必须已经按升序或降序排列。下面是该算法的6一个非正式描述,在进行查找之前,首先要求用户输入一个整数,我们称之为 searchVal:
下面是汇编语言实现:
前期逻辑整理,并写下相应的逻辑代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 二分查找算法的原理如下: 1. 设置查找区间:first = 0;last= n;当 first > last,跳出循环。 2. 若查找区间[low, high]不存在,则查找失败;否则转步骤3 3. 取中间位mid = (low + high) / 2;比较 target 与 arr[mid],有以下三种情况: 3.1 若 target < arr[mid],则high = mid - 1;查找在左半区间进行,转步骤2; 3.2 若 target > arr[mid],则low = mid + 1;查找在右半区间进行,转步骤2; 3.3 若 target = arr[mid],则查找成功,返回 mid 值; 4. 如果找到该值,返回相应指针;如果错误,返回 -1 局部变量定义: COunt(数组长度)= LENGTHOF array SearchVal(要查找的值) 1. if(first<=last) ;-------------------------------------------------------- mov first,0 mov ebx, COunt dec ebx mov last,ebx L0: mov eax,first cmp eax,last jg L4 L4: mov eax,-1 L5: ret ;---------------------------------------------------------- 2. 比较判断 ;---------------------------------------------------------- add ebx,first shr ebx,1 ;mov mid,ebx mov eax,array[ebx*TYPE array] cmp SearchVal,eax jl L1 jg L2 je L3 L1: dec ebx mov last,ebx jmp L0 L2: inc ebx mov first,ebx jmp L0 L3: lea eax,array[ebx*TYPE array] jmp L5 ;---------------------------------------------------------- 3. 整合 ;---------------------------------------------------------- mov first,0 mov ebx, COunt dec ebx mov last,ebx L0: mov eax,first cmp eax,last jg L4 add ebx,first shr ebx,1 ;mov mid,ebx mov eax,array[ebx*TYPE array] cmp SearchVal,eax jl L1 jg L2 je L3 L1: dec ebx mov last,ebx jmp L0 L2: inc ebx mov first,ebx jmp L0 L3: lea eax,array[ebx*TYPE array] jmp L5 L4: mov eax,-1 L5: ret ;----------------------------------------------------------
调试验证
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 INCLUDE Irvine32.inc BinarySearch PROTO, pArray:PTR DWORD, Count:DWORD, SearchVal:DWORD .data array1 DWORD '0','1','2','3','4','5','6','7','8','9' .code main PROC INVOKE BinarySearch,ADDR array1,LENGTHOF array1,'7' exit main ENDP ;----------------------------------------------- BinarySearch PROC USES ebx edx esi edi, pArray:PTR DWORD, Count:DWORD, SearchVal:DWORD LOCAL first:DWORD, last:DWORD ;功能:使用二分查找法,查找整数数组中的值。 ;入口参数:数组指针、数组大小、要查找的值 ;出口参数:如果查到该值,EAX 返回查找到该值的数组指针;反之 EAX=-1 ;----------------------------------------------- mov first,0 mov ebx, Count dec ebx mov last,ebx L0: mov eax,first cmp eax,last jg L4 add ebx,first shr ebx,1 ;mov mid,ebx mov eax,array1[ebx*TYPE array1] cmp SearchVal,eax jl L1 jg L2 je L3 L1: dec ebx mov last,ebx jmp L0 L2: inc ebx mov first,ebx jmp L0 L3: lea eax,array1[ebx*TYPE array1] jmp L5 L4: mov eax,-1 L5: ret BinarySearch ENDP END main
测试程序 下面写一个测试程序,再熟悉一下冒泡排序和二分查找。
用随机整数填充数组
显示数组
使用冒泡排序算法对数组进行排序
重新显示数组
要求用户输入一个整数
使用二分查找算法在数组中查找用户输入整数
显示二分查找结果
不同的过程放在单独的源代码文件中以便定位和编辑。
除了 B_main 之外的所有模块都以在 Bsearch.inc 文件中声明。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ;Bsearchinc - prototypes for procedures usesd in ;the BubbleSort / BinarySearch program. BinarySearch PROTO, pArray:PTR DWORD, Count:DWORD, SearchVal:DWORD FillArray PROTO, pArray:PTR DWORD, Count:DWORD, LowerRange:SDWORD, UpperRange:SDWORD PrintArray PROTO, pAray:PTR DWORD, Count:PTR DWORD BubbleSort PROTO, pArray:PTR DWOD, Count:DWORD
下面是主模块 B_main.asm 的清单:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 ;--------------------------- ;程序名:B_main.asm ;功能:使用冒泡排序和二分查找,搜索用户输入的字符。 ;作者:9unk ;编写时间:2023-2-29 ;---------------------------- INCLUDE Irvine32.inc INCLUDE Bsearch.inc LOWVAL = -5000 HIGHVAL = +5000 ARRAY_SIZE = 50 .data array DWORD ARRAY_SIZE DUP(?) .code main PROC call Randomize ;向数组中填充随机数 INVOKE FillArray,ADDR array,ARRAY_SIZE,LOWVAL,HIGHVAL ;显示数组 INVOKE PrintArray,ADDR array,ARRAY_SIZE call WaitMsg call Crlf call Crlf ;数组排序 INVOKE Bubblesort,ADDR array,ARRAY_SIZE INVOKE PrintArray,ADDR array,ARRAY_SIZE ;查找关键字 call AskForSearchVal INVOKE BinarySearch,ADDR array,ARRAY_SIZE,eax call ShowResults exit main ENDP ;------------------------------------------- AskForSearchVal PROC ; ;功能:提示用户输入有符号整数 ;入口参数:无 ;返回值:EAX = 用户输入的值 ;------------------------------------------- .data prompt BYTE "Enter a signed decimal integer " BYTE "int the range of -5000 to +5000 " BYTE "to find in the array: ",0 .code call Crlf mov edx,OFFSET prompt call WriteString call ReadInt ret AskForSearchVal ENDP ;-------------------------------------------- ShowResults PROC ;显示搜索字符的结果。 ;入口参数:EAX = 要删除的符号。 ;返回值:无 ;------------------------------------------- .data msg1 BYTE "The value was not found.",0 msg2 BYTE "The value was found at position ",0 .code .IF eax == -1 mov edx,OFFSET msg1 call WriteString .ELSE mov edx,OFFSET msg2 call WriteString call WriteHex .ENDIF call Crlf call Crlf ret ShowResults ENDP END main
PrintArray:下面是包含 PrintArray 过程的模块清单。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 ;--------------------------- ;程序名:PtrArray.asm ;功能:输出 32 位有符号数。 ;作者:9unk ;编写时间:2023-2-29 ;---------------------------- INCLUDE Irvine32.inc .code ;-------------------------------------------------------- PrintArray PROC USES eax ecx edx esi, pAray:PTR DWORD, Count:PTR DWORD ;功能:将32位有符号十进制整数的数组写入标准输出,用逗号分隔 ;入口参数:数组指针,数组大小 ;返回值:无 ;-------------------------------------------------------- .data comma BYTE ", ",0 .code mov esi,pAray mov ecx,Count cld L1: lodsd call WriteInt mov edx,OFFSET comma call WriteString loop L1 call Crlf ret PrintArray ENDP END
FillArray:下面是包含 FillArray 过程的清单。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 ;--------------------------- ;程序名:FillArray.asm ;功能:使用 32 位随机有符号整数填充数组 ;作者:9unk ;编写时间:2023-2-29 ;---------------------------- INCLUDE Irvine32.inc .code ;-------------------------------------------------------- FillArray PROC USES eax edi ecx edx, pArray:PTR DWORD, Count:DWORD, LowerRange:SDWORD, UpperRange:SDWORD ;功能:使用 32 位随机(LowerRange~UpperRange-1之间)有符号整数填充数组 ;返回值:无 ;-------------------------------------------------------- mov edi,pArray mov ecx,Count mov edx,UpperRange sub edx,LowerRange L1: mov eax,edx call RandomRange add eax,LowerRange stosd loop L1 ret FillArray ENDP END
以下是 Bsearch.asm 模块:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 ;--------------------------- ;程序名:Bsearch.asm ;功能:使用二分查找法,查找整数数组中的值。 ;作者:9unk ;编写时间:2023-2-29 ;---------------------------- INCLUDE Irvine32.inc .code ;----------------------------------------------- BinarySearch PROC USES ebx edx esi edi, pArray:PTR DWORD, Count:DWORD, SearchVal:DWORD LOCAL first:DWORD, last:DWORD ;功能:使用二分查找法,查找整数数组中的值。 ;入口参数:数组指针、数组大小、要查找的值 ;出口参数:如果查到该值,EAX 返回查找到该值的数组指针;反之 EAX=-1 ;----------------------------------------------- mov first,0 mov ebx, Count dec ebx mov last,ebx mov SearchVal,eax mov esi,pArray L0: mov eax,first cmp eax,last jg L4 add ebx,first shr ebx,1 ;mov eax,ds:pArray[ebx*4] ;错,pArray 指向的是局部变量 pArray 的指针 mov eax,ds:[esi+ebx*4] cmp SearchVal,eax jl L1 jg L2 je L3 L1: dec ebx mov last,ebx jmp L0 L2: inc ebx mov first,ebx jmp L0 L3: ;lea eax,ds:pArray[ebx*4] ;错,pArray 指向的是局部变量 pArray 的指针 lea eax,ds:[esi+ebx*4] jmp L5 L4: mov eax,-1 L5: ret BinarySearch ENDP END
以下是 Bsort.asm 模块:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 ;--------------------------- ;程序名:Bsort.asm ;功能:32位整数数组冒泡排序 ;作者:9unk ;编写时间:2023-2-29 ;---------------------------- INCLUDE Irvine32.inc .code ;------------------------------------------------ BubbleSort PROC USES eax ecx esi, pArray:PTR DWOD, Count:DWORD ;32位整数数组冒泡排序 ;入口参数:数组指针,数组大小 ;出口参数:无 ;------------------------------------------------ mov ecx,Count dec ecx L1: push ecx mov esi,pArray L2: mov eax,[esi] cmp [esi+4],eax jg L3 xchg eax,[esi+4] mov [esi],eax L3: add esi,4 loop L2 pop ecx loop L1 ret BubbleSort ENDP END