字串(string)以字元陣列的形式出現,可以基於 ASCII 或 Unicode。 例如,ASCII 字串 “ABC123” 是十六進制 ASCII 陣列 41h,42h,43h,31h,32h,33h,0h。 末尾的 0h 是 NULL 字元,請記住,在進行編程和偵錯時,字元和字元陣列通常以十六進制形式顯示。
宣告字串必須以引號括起字串,以空字元(null)結尾,NASM 使用字面值(literal value)的 0 作為字串最後一個位元組,而無需使用 \0 逸出序列。如直接使用 string literals 不需空字元。
在 NASM 中換行僅需要把 LF(Line Feed)的十六進制碼 0Ah 作一個 BYTE 元素插入到字串中,而無需插入 CR(Carriage Return)的十六進制碼 0Dh 或是 \r 或 \n 逸出序列。
在 NASM 字串中使用引號作字元需包括在相反引號內,單引號在雙引號內,雙引號在單引號內,而非使用 \" 逸出序列。
字串宣告方式:
greeting: db "Hello World", 0Ah, '"Hello" is a word', 0
在討論字串指令之前,我們必須先認識可與字串指令一起使用的兩類指令:方向指令和重覆指令。
方向指令:
cld(Clear direction flag):重覆字串指令時,從左到右讀取字元(即從低到高的記憶體位址)。
std:(Set direction flag):重覆字串指令時,從右到左讀取字元(即從高到低的記憶體位址)。
重覆指令:
rep(Repeat):cx/ecx ≠ 0 時重覆
repe/repz(Repeat while equal/zero):cx/ecx ≠ 0 and zf = 1 時重覆
repne/repnz(Repeat while not equal/not zero):cx/ecx ≠ 0 and zf = 0 時重覆
有了方向指令和重覆指令,我們便可以使用字串指令了:
movs:movs 與 mov 指令相似,但專門用於將字元從一個位置複製到另一位置。 movs 隱式地將資料從 si/esi(source)參考的存儲位置複製到 di/edi(destination)參考的存儲位置。
cmps:cmps 與 cmp 指令相似,但專門用於比較 si / esi 和 di / edi 參考的字串。 cmps 通常與 repe / repz 一起使用,這樣當 source 中的字元與 destination 中的相應字元不相等時,比較就會終止。
movs(move string)指令:與 mov 相似,但跟 mov 有兩個區別:
1. movs 的 source operand 和 destination operand 是分別隱式使用 esi 和 edi。不可指定其他 operand。
2. 無論是在哪一款組譯器中 movs 都有 size identifier 作後綴(例如 BYTE 是 movsb,WORD 是 movsw)。
在使用 movs 時,寄存器 esi 和 edi 應分別包含指向要複製資料的記憶體地址(source)和要複製到的記憶體地址(destination)。movs 和 rep 同用時,CPU 會根據 movs 後綴指示的 size identifier 將記憶體位址增加或減少若干位元組數(取決於 df),以便處理下一個字元。 因為字串指令每次可以對 1 到 16 個位元組的資料進行處理,所以字串指令需要知道每次重複需讀取多少個位元組數才能正確定址每個字元。
假設我們有一些資料存儲在記憶體中,我們希望將資料複製到另一個位置。已知資料為 10 個字元長,我們可以相應地加載 counter register(cx/ecx),source register(si/esi)和 destination register(di/edi)。然後,我們可以設置方向來處理字串並最終執行複製。
segment .data
str1: db "abcdefghi", 0
len: EQU ($ - str1)
segment .bss
str2: resb 10
segment .text
...
mov esi, str1
mov edi, str2
mov ecx, len
cld
rep movsb
以下是在執行 rep 或變體以及字串指令時自動發生的步驟。
1. 重覆指令檢查 counter register(cx/ecx)的值是否大於 0。如果值為 0,則重覆過程結束。如果該值大於 0,則重覆過程繼續。
2. 字串指令執行,根據結果設置相關的 flag,並根據指令和 direction flag 遞增或遞減 si/esi 和 di/edi。
3. 如果重覆指令是 repe/repne 和 repz/repnz 則額外檢查 zero flag 以確定是否應重覆。如果 zero flag 指示不應發生重覆,則過程結束。
4. 如果第 2 步和第 3 步(如果適用)成功執行,則 counter register 遞減並返回到第 1 步。
每個字串指令都使用 direction flag(df)將 esi 和 edi 中的記憶體位址增加或減少指定的位元組數,以便該字串指令在陣列中向所需的方向前進 。 即使 rep 不伴隨字串指令,也會發生記憶體位址增量或減量。
假如不使用方向指令和重覆指令,以上程式碼等價於以下程式碼(注意 mov 指令的兩個 operand 不能同時指向記憶體,必須使用寄存器作中介):
...
segment .text
...
mov ecx, 10
CopyStr:
mov al, [str1 + ecx - 1]
mov [str2 + ecx - 1], al
loop CopyStr
ecx 達至 0 循環就會停止,所以用 [10-1] 到 [1-1] 為索引代替 [9] 到 [0]。方向指令和重覆指令的組合讓我們可以自動處理資料,而無需手動編寫循環,並且增加程式碼的可讀性。
cmps(compare string)指令與 cmp 指令相似。cmps 和 movs 同樣不採用 operand,而是隱式使用 esi 和 edi 作為要比較的資料的記憶體位址。同樣,cmps 一般與 rep 或其變體結合使用。在沒有 rep 的情況下執行 cmps 只會比較單個字元,而不是整個字串。cmps 也必須有 size identifier 作後綴(例如 cmpsb, cmpsw, cmpsd)。
cmps 除了需要重複執行使其能在字串中逐個字元移動,還必須在每次重複中執行一次檢查,以確定字串中的字元是否相等。 rep 僅重複執行指令而不會檢查 flag,所以我們必須使用重複變體之一(repe / repz 或 repne / repnz)。例如,repe / repz 僅在字元相等(zf = 1)時才繼續重複。 cmps 通過對 edi 所參考的字元執行隱式減法來比較每個相應的字元,從而修改了 CPU 的 zf。例如:
segment .data
str1: db "abcdefghi", 0
str2: db "abcddfghi", 0
len: EQU ($ - str2)
segment .text
...
mov esi, str1
mov edi, str2
mov ecx, len
cld
repe cmpsb
jnz notequal
...
jmp done
notequal:
...
done:
...
執行完 repe cmps 之後,您可能會檢查 ecx 是否為零:以為「ecx ≠ 0 表示字串不相等;ecx = 0 表示字串相等」。這大部份時間是對的,除了一個情況:字串的最後一個字元不同時,這時 ecx 會是 0 而字串不相等。所以我們應檢查 zf 是否為 1 而不是 ecx 是否為 0 來判斷兩字串相等。
如果要對不同長度的字串執行 cmps,應使用兩個長度中較短的一個作為 ecx 值。這樣做可確保重複不超過較短字串的字元數,否則會導致錯誤的結果。請記住,Assembly 不執行自動邊界檢查。
還有更多的字串指令可以繼續看:x86 Assembly 的字串指令、方向指令與重覆指令(續)