540 likes | 765 Vues
單元 9: 字串與陣列. 單元概要. 字串的基本指令 經過篩選的字串程序 二維陣列 整數陣列的搜尋和排序. 字串的基本指令. MOVSB, MOVSW, 和 MOVSD CMPSB, CMPSW, 和 CMPSD SCASB, SCASW, 和 SCASD STOSB, STOSW, 和 STOSD LODSB, LODSW, 和 LODSD. MOVSB, MOVSW, 和 MOVSD (2 之 1). MOVSB、MOVSW 和 MOVSD 指令用於從由 ESI 所指向的記憶體位置,複製資料到由 EDI 所指向的記憶體位置。. .data
E N D
單元概要 • 字串的基本指令 • 經過篩選的字串程序 • 二維陣列 • 整數陣列的搜尋和排序
字串的基本指令 • MOVSB, MOVSW, 和MOVSD • CMPSB, CMPSW, 和CMPSD • SCASB, SCASW, 和SCASD • STOSB, STOSW, 和STOSD • LODSB, LODSW, 和LODSD
MOVSB, MOVSW, 和MOVSD (2之1) • MOVSB、MOVSW 和 MOVSD 指令用於從由 ESI 所指向的記憶體位置,複製資料到由 EDI 所指向的記憶體位置。 .data source DWORD 0FFFFFFFFh target DWORD ? .code mov esi,OFFSET source mov edi,OFFSET target movsd
MOVSB, MOVSW, 和MOVSD (2之2) • 方向旗標將用於決定 ESI 和 EDI 會遞增或遞減 • MOVSB 遞增或遞減 1 • MOVSW 遞增或遞減 2 • MOVSD 遞增或遞減 4
方向旗標 • 字串基本指令會根據方向旗標的狀態,來遞增或遞減 ESI 與 EDI。 • DF = clear (0): 遞增 ESI and EDI • DF = set (1): 遞減 ESI and EDI 程式設計員能使用 CLD 和 STD 指令,明確地更改方向旗標: CLD ; 使方向旗標處於清除狀態 ( 順向 ) STD ; 使方向旗標處於設定狀態 ( 逆向 )
使用重覆性指令前置字 • REP 指令執行在 MOVSB, MOVSW, or MOVSD之前。 • 這個指令將會使用ECX作為計數器,來執行重複的動作。 • 範例:複製 20 個雙字組整數到 target .data source DWORD 20 DUP(?) target DWORD 20 DUP(?) .code cld ; direction = forward mov ecx,LENGTHOF source ; set REP counter mov esi,OFFSET source mov edi,OFFSET target rep movsd
練習 • 使用 MOVSD 刪除下列 doubleword 陣列的第一元素.所有的後來陣列值一定要對於開始向前移動一個位置 • 陣列: array DWORD 1,1,2,3,4,5,6,7,8,9,10 .data array DWORD 1,1,2,3,4,5,6,7,8,9,10 .code cld mov ecx,(LENGTHOF array) - 1 mov esi,OFFSET array+4 mov edi,OFFSET array rep movsd
CMPSB, CMPSW, 和 CMPSD • CMPSB、CMPSW 和 CMPSD 指令都用於,將由 ESI 所指向的記憶體運算元,與由 EDI 所指向的記憶體運算元進行比較 • CMPSB 比較位元組 • CMPSW 比較字組 • CMPSD 比較雙字組 • 重複性指令前置字 (REP) 經常的被使用
比較雙字組資料 如果 source > target,則跳越到 L1;如果 source <= target,則跳越到 L2 .data source DWORD 1234h target DWORD 5678h .code mov esi,OFFSET source mov edi,OFFSET target cmpsd ;比較雙字組資料 ja L1 ;如果 source > target,則跳越到 L1 jmp L2 ;如果 source <= target,則跳越到 L2
練習 • 修正程式在先前幻燈片宣佈資料和目標如WORD變數,作任何其他必要的改變.
比較數組 REPE 字首會重複執行比較運算,並且自動地增加 ESI 和 EDI 的值,直到 ECX 等於零, 或發現任何一對雙字組是不相同的為止。 .data source DWORD COUNT DUP(?) target DWORD COUNT DUP(?) .code mov ecx,COUNT ; 重複動作的計數器 mov esi,OFFSET source mov edi,OFFSET target cld ; 方向 = 順向 repe cmpsd ; 在相等時持續執行 cmpsd
Source is smaller Screen output: 範例: 比較二個字串(3之1) 下列的程式使用 CMPSB,去比較兩個相同長度的字串。 而 REPE 指令前置字會導致 CMPSB 持續遞增 ESI 和 EDI 的值,並且一個接著一個地比較字元,直到在兩字串之間發 現不同字元為止: .data source BYTE "MARTIN " dest BYTE "MARTINEZ" str1 BYTE "Source is smaller",0dh,0ah,0 str2 BYTE "Source is not smaller",0dh,0ah,0
範例: 比較二個字串(3之2) • .code • main PROC • cld ; direction = forward • mov esi,OFFSET source • mov edi,OFFSET dest • mov cx,LENGTHOF source • repe cmpsb • jb source_smaller • mov edx,OFFSET str2 ; "source is not smaller" • jmp done • source_smaller: • mov edx,OFFSET str1 ; "source is smaller" • done: • call WriteString • exit • main ENDP • END main
範例: 比較二個字串(3之3) • ESI 和 EDI 所指向的位置,是位於發現兩個字串是不同的位置處的後面一個位置。
練習 • 從先前二個幻燈片修正字串對照程式,為資料和目的字串促使使用者. • 樣本輸出: Input first string: ABCDEFG Input second string: ABCDDG The first string is not smaller.
控制螢幕(樣板) Source is smaller
SCASB, SCASW, 和 SCASD • SCASB、SCASW 和 SCASD 指令分別用於,將 AL/AX/EAX 中的值與由 EDI 所指向的位 元組、字組或雙字組,進行比較。 • 想在字串或陣列中尋找某單一個值的時候,這些指令會特別有用: • 在一些長字串或陣列中尋找特定的要素。 • Search for the first element that does not match a given value.
SCASB 範例 在字串alpha中尋找字母 'F' : .data alpha BYTE "ABCDEFGH",0 .code mov edi,OFFSET alpha mov al,'F' ; search for 'F' mov ecx,LENGTHOF alpha cld repne scasb ; repeat while not equal jnz quit dec edi ; EDI points to 'F' JNZ 指令的用途是什麼?
STOSB, STOSW, and STOSD • STOSB、STOSW 和 STOSD 指令分別用於,將 AL/AX/EAX 的內容存放在記憶體中,由 EDI 所指向的位移處。 • 範例:以下的程式碼會將 string1 中的每個位元組,初始化為 0FFh .data Count = 100 string1 BYTE Count DUP(?) .code mov al,0FFh ; 所要存放的值 mov edi,OFFSET string1 ; EDI 指向目標運算元 mov ecx,Count ; 字元的個數 cld ; 方向 = 順向 rep stosb ; 填入 AL 的內容值
LODSB, LODSW, and LODSD • LODSB、LODSW 和 LODSD 指令會從記憶體中由 ESI 所指向的位置,載入一個位元組、 字組或雙字組,到 AL/AX/EAX 中。 .data array 1,2,3,4,5,6,7,8,9 dest 9 DUP(?) .code mov esi,OFFSET array mov edi,OFFSET dest mov ecx,LENGTHOF array cld L1: lodsb or al,30h stosb loop L1
陣列乘法的範例 以下的程式會將雙字組陣列的每個元素,乘以一個常數值。 .data array DWORD 1,2,3,4,5,6,7,8,9,10 multiplier DWORD 10 .code cld ; direction = up mov esi,OFFSET array ; source index mov edi,esi ; destination index mov ecx,LENGTHOF array ; loop counter L1: lodsd ; copy [ESI] into EAX mul multiplier ; multiply by a value stosd ; store EAX at [EDI] loop L1
練習 • 寫個程式轉換各自取出二進制編碼的十進制位元屬於陣列進入ASCII十進位位元而且複製到新的陣列. .data array BYTE 1,2,3,4,5,6,7,8,9 dest BYTE (LENGTHOF array) DUP(?) mov esi,OFFSET array mov edi,OFFSET dest mov ecx,LENGTHOF array cld L1: lodsb ; load into AL or al,30h ; convert to ASCII stosb ; store into memory loop L1
經過篩選的字串程序 • Str_compare 程序 • Str_length 程序 • Str_copy 程序 • Str_trim 程序 • Str_ucase 程序 我們將示範說明幾個能操作空字元終止字串 (null-terminated strings) 的程序, 其中,這些程序都是選自 Irvine32 和 Irvine16 函式庫。
Str_compare 程序 • Str_compare 程序用於比較兩個字串 • 這個程序並沒有回傳值,不過,進位和零值旗標可以詮釋此程序處理的結果 • 示範: Str_compare PROTO, string1:PTR BYTE, ; pointer to string string2:PTR BYTE ; pointer to string 在範例, 如果 string1 > string2, CF=0, ZF=0 或著如果 string1 < string2, CF=1, ZF=0
Str_compare 來源編碼 Str_compare PROC USES eax edx esi edi, string1:PTR BYTE, string2:PTR BYTE mov esi,string1 mov edi,string2 L1: mov al,[esi] mov dl,[edi] cmp al,0 ; string1 是否結束? jne L2 ; 否 cmp dl,0 ; 是: string2 是否結束? jne L2 ; 否 jmp L3 ; 是: ZF = 1,離開此程序 L2: inc esi ; 指向下一個位置 inc edi cmp al,dl ; 兩個字元相同嗎? je L1 ; 是:繼續執行迴圈 L3: ret Str_compare ENDP
範例: .data myString BYTE "abcdefg",0 .code INVOKE Str_length, ADDR myString ; EAX = 7 Str_length 程序 • Str_length 程序用於回傳字串的長度到 EAX 暫存器中。 • 示範: Str_length PROTO, pString:PTR BYTE ; pointer to string
Str_length 來源編碼 Str_length PROC USES edi, pString:PTR BYTE ;指向字串的指標 mov edi,pString mov eax,0 ;字元計數器 L1: cmp byte ptr [edi],0 ;字串尾端了嗎 ? je L2 ;是:則離開 inc edi ;否:指向下一個字元 inc eax ;將計數器加 1 jmp L1 L2: ret Str_length ENDP
Str_copy 程序 • Str_copy 程序用於從來源運算元所定址的位置,複製一個空字元終止字串,到目的運算元 所定址的位置。 • 示範: Str_copy PROTO, source:PTR BYTE, ; pointer to string target:PTR BYTE ; pointer to string 請參看 CopyStr.asm 程式來作為這個程序的示範解說。
Str_copy 來源編碼 Str_copy PROC USES eax ecx esi edi, source:PTR BYTE, ;來源字串 target:PTR BYTE ;目的字串 INVOKE Str_length,source ;EAX = 來源字串的長度 mov ecx,eax ;REP 的執行次數 inc ecx ;針對空位元組而加 1 mov esi,source mov edi,target cld ;方向 = 順向 rep movsb ;複製字串 ret Str_copy ENDP
範例: .data myString BYTE "Hello###",0 .code INVOKE Str_trim, ADDR myString myString = "Hello" Str_trim 程序 • Str_trim程序用於將空字元終止字串中被指定的尾隨字元全部移除掉。 • 示範: Str_trim PROTO, pString:PTR BYTE, ; points to string char:BYTE ; char to remove
Str_trim 程序 • 這個程式的邏輯很有趣,因為讀者必須檢查幾種可能的情況 ( 這裡以 # 代表被指定的尾隨 字元 ): • 字串是空的。 • 字串含有其他字元,而且其後接著一個或多個尾隨字元,例如 “Hello##”。 • 字串只含有一個字元,而這個字元即為尾隨字元,例如 "#"。 • 字串沒有尾隨字元,例如 "Hello" 或 "H"。 • 字串含有一個或多個尾隨字元,而且這些尾隨字元之後還接著一個或多個非尾隨字元, 例如 "#H" 或 "###Hello"。
Str_trim 來源編碼 Str_trim PROC USES eax ecx edi, pString:PTR BYTE, ;指向字串的指標 char:BYTE ;要移除的字元 mov edi,pString INVOKE Str_length,edi ;將字串長度回傳到 EAX cmp eax,0 ;字串長度為零嗎? je L2 ;是: 則離開此程序 mov ecx,eax ;否: 計數器 = 字串長度 dec eax add edi,eax ;使 EDI 指向最後一個字元 mov al,char ;想要移除的字元 std ;方向 = 逆向 repe scasb ;跳過想要移除的字元 jne L1 ;要移除第一個字元嗎? dec edi ;調整 EDI: ZF=1 && ECX=0 L1: mov BYTE PTR [edi+2],0 ;插入空位元組 L2: ret Str_trim ENDP
範例: .data myString BYTE "Hello",0 .code INVOKE Str_ucase, ADDR myString Str_ucase 程序 • Str_ucase 程序用於將一個字串,全都轉換成大寫的字元。 而且,這個程序不會回傳任何 值。 • 示範: Str_ucase PROTO, pString:PTR BYTE ; pointer to string
Str_ucase 來源編碼 Str_ucase PROC USES eax esi, pString:PTR BYTE mov esi,pString L1: mov al,[esi] ;取得字元 cmp al,0 ;字串的尾端? je L3 ;是: 則離開此程序 cmp al,'a' ;字元的值小於 "a" 嗎? jb L2 cmp al,'z' ;字元的值大於 "z" 嗎? ja L2 and BYTE PTR [esi],11011111b ;轉換為大寫字元 L2: inc esi ;指向下一個字元 jmp L1 L3: ret Str_ucase ENDP
二維陣列 • 基底-索引運算元 • 基底-索引-移位運算元
基底-索引運算元 • 基底 - 索引運算元會將兩個暫存器的值相加,並且產生一個位移位址,其中的兩個暫存器稱 為基底 (base) 暫存器和索引 (index) 暫存器。 • 任何 32 位元通用暫存器 都可以當作基底和索引暫存器。 • 在 16 位元模式下,基底暫存器必須是 BX 或 BP。 ( 不過,除非是要定址堆疊記憶體,否則應該避免使用 BP 或 EBP。)
應用結構 一個基礎索引的普通申請演說必須做結構 (第 10 章) 的陣列發表演說.下列的 definds 叫做包含 X 和 Y 的 COORD 的結構屏幕坐標 : COORD STRUCT X WORD ? ; offset 00 Y WORD ? ; offset 02 COORD ENDS 然後我們能定義 COORD 目標的陣列: .data setOfCoordinates COORD 10 DUP(<>)
應用結構 下列的編碼使陣列成迴路而且顯示各自Y的座標: mov ebx,OFFSET setOfCoordinates mov esi,2 ; offset of Y value mov eax,0 L1: mov ax,[ebx+esi] call WriteDec add ebx,SIZEOF COORD loop L1
基底-索引-移位運算元 • A基底 - 索引 - 移位運算元會將移位、基底暫存器、索引暫存器和可選用的比例因子組合起 來,產生一個有效位址。 • 任何 32 位元 通用暫存器都可以當作基底和索引暫存器。 • 常見的編排: [ base + index + displacement ] displacement [ base + index ]
選擇格式: table BYTE 10h,20h,30h,40h,50h,60h,70h, 80h,90h,0A0h, 0B0h,0C0h,0D0h, 0E0h,0F0h NumCols = 5 雙字組陣列的例子 想像一張表格用三列和五行資料,能在圖上被以任何的格式安排: table BYTE 10h, 20h, 30h, 40h, 50h BYTE 60h, 70h, 80h, 90h, 0A0h BYTE 0B0h, 0C0h, 0D0h, 0E0h, 0F0h NumCols = 5
雙字組陣列的例子 下列的編碼填上在表格元素儲存在一列二行: RowNumber = 1 ColumnNumber = 2 mov ebx,NumCols * RowNumber mov esi,ColumnNumber mov al,table[ebx + esi]
整數陣列的搜尋和排序 • 氣泡排序 • 氣泡排序法 (bubble sort) 會從位置 0 和位置 1 開始,比較陣列中的每對陣列值。 • 二元搜尋 • 在平常的程式設計應用中,陣列搜尋是最常用的資料操作的其中一部份。 對於一個小陣列而言,執行循序搜尋是很容易的;在 循序搜尋的過程中,會從陣列的開端開始進行,然後依序檢視陣列中的每個元素,直到發現符合搜尋條件的元素為止。
氣泡排序 如果被比 較的兩個值是相反順序的,那麼它們將會交換位置,下圖進行氣泡排序法 時,一次完整通過整數陣列的處理過程
氣泡排序虛擬碼 我們將使用 N 來代表陣列的大小,使用 cx1 來代表外層迴圈計數器,而 cx2 則表 示內層迴圈計數器: cx1 = N - 1 while( cx1 > 0 ) { esi = addr(array) cx2 = cx1 while( cx2 > 0 ) { if( array[esi] < array[esi+4] ) exchange( array[esi], array[esi+4] ) add esi,4 dec cx2 } dec cx1 }
完成氣泡排序 BubbleSort PROC USES eax ecx esi, pArray:PTR DWORD,Count:DWORD mov ecx,Count dec ecx ; decrement count by 1 L1: push ecx ; save outer loop count mov esi,pArray ; point to first value L2: mov eax,[esi] ; get array value cmp [esi+4],eax ; compare a pair of values jge L3 ; if [esi] <= [edi], skip xchg eax,[esi+4] ; else exchange the pair mov [esi],eax L3: add esi,4 ; move both pointers forward loop L2 ; inner loop pop ecx ; retrieve outer loop count loop L1 ; else repeat outer loop L4: ret BubbleSort ENDP
二維搜尋 • 使用稱為 first 和 last 的下標,來標示陣列的搜尋範圍。 如果 first > last,則跳出搜尋的 程序,並且指出,無法找到吻合的元素。 • 計算介於陣列下標 first 和 last 之間的陣列中點。 • 將 searchVal 與位於陣列中點的整數進行比較: • 如果兩個值相等,則從程序中回傳這個中點的位置到 EAX。 這 個回傳值代表,已經從陣列中 找到符合條件的值。 • 另一方面,如果 searchVal 比位於中點的值大,那麼便將陣列下標 first 重新設定為,比中點高一個位置的位置。 • 或者,如果 searchVal 比位於中點的值小,那麼便將陣列下標last 重新設定為,比中點低一個 位置的位置。 • 二元搜尋都會被描述成是一種 O(log n) 演算法: • 當陣列元素個數增加為 n 倍時,平均搜尋時間只會增加為 log n 倍。
二維搜尋虛擬碼 int BinSearch( int values[], const int searchVal, int count ) { int first = 0; int last = count - 1; while( first <= last ) { int mid = (last + first) / 2; if( values[mid] < searchVal ) first = mid + 1; else if( values[mid] > searchVal ) last = mid - 1; else return mid; // success } return -1; // not found }
完成二維搜尋 (1 之 3) BinarySearch PROC uses ebx edx esi edi, pArray:PTR DWORD, ; pointer to array Count:DWORD, ; array size searchVal:DWORD ; search value LOCAL first:DWORD, ; first position last:DWORD, ; last position mid:DWORD ; midpoint mov first,0 ; first = 0 mov eax,Count ; last = (count - 1) dec eax mov last,eax mov edi,searchVal ; EDI = searchVal mov ebx,pArray ; EBX points to the array L1: ; while first <= last mov eax,first cmp eax,last jg L5 ; exit search