我是大會的初學者,我有一個簡單的問題。這是我的代碼:
BITS 64 ; 64?bit mode
global strchr ; Export 'strchr'
SECTION .text ; Code section
strchr:
mov rcx, -1
.loop:
inc rcx
cmp byte [rdi rcx], 0
je exit_null
cmp byte [rdi rcx], sil
jne .loop
mov rax, [rdi rcx]
ret
exit_null:
mov rax, 0
ret
這個編譯但不起作用。如您所見,我想重現函式 strchr 。當我用 printf 測驗我的函式時,它崩潰了(問題不在于測驗)。我知道我可以直接 INC rdi 進入 rdi 引數并將其回傳到我想要的位置。但我只想知道是否有辦法在 rcx 位置回傳 rdi 來修復我的代碼并可能改進它。
uj5u.com熱心網友回復:
您的函式strchr似乎需要兩個引數:
- 指向字串的指標
RDI,和 - 指向一個字符的指標
RSI。
暫存器rcx用作字串內的索引?在這種情況下,您應該使用al而不是cl. 請注意,您不會限制搜索大小。當RSI在字串中找不到參考的字符時,很可能會觸發例外。也許您應該al在.[rdi rcx]al=0
如果您希望它回傳指向字串中第一次出現的字符的指標,只需
替換mov rax,[rdi rcx]為lea rax,[rdi rcx].
uj5u.com熱心網友回復:
您的代碼(來自編輯版本 2)執行以下操作:
char* strchr ( char *p, char x ) {
int i = -1;
do {
if ( p[i] == '\0' ) return null;
i ;
} while ( p[i] != x );
return * (long long*) &(p[i]);
}
正如@vitsoft 所說,您的意圖是回傳一個指標,但在第一個回傳(在匯編中)是回傳從找到的字符的地址加載的單個四字,8 個字符而不是地址。
在回圈中間遞增是不尋常的。從 -1 開始索引也很奇怪。在第一次迭代中,回圈繼續條件查看p[-1],這不是一個好主意,因為這不是您被要求搜索的字串的一部分。如果該位元組恰好是 nul 字符,它將立即停止搜索。
如果您等到兩個測驗都執行后才遞增,那么您將不會參考 p[-1],并且您也可以從 0 開始索引,這會更常見。
您可能會考慮將字符捕獲到暫存器中,而不是三次使用復雜的尋址模式。
此外,您可以將指標推進rdi并完全放棄索引變數。
這是在 C 中的:
char* strchr ( char *p, char x ) {
for(;;) {
char c = *p;
if ( c == '\0' )
break;
if ( c == x )
return p;
p ;
}
return null;
}
uj5u.com熱心網友回復:
感謝您的幫助,我終于做到了!感謝 Erik 的回答,我修正了一個愚蠢的錯誤。我正在將 str[-1] 與 NULL 進行比較,所以它出錯了。隨著 vitsoft 的回答,我將 mov 切換為 lea 并且它起作用了!有我的代碼:
strchr:
mov rcx, -1
.loop:
inc rcx
cmp byte [rdi rcx], 0
je exit_null
cmp byte [rdi rcx], sil
jne .loop
lea rax, [rdi rcx]
ret
exit_null:
mov rax, 0
ret
uj5u.com熱心網友回復:
當前版本中剩下的唯一錯誤是加載 8 個位元組的 char 資料作為回傳值,而不是僅僅進行指標數學運算,使用mov代替lea. (在洗掉了各種編輯并添加了不同的錯誤之后,這反映在不同的答案談論不同的代碼)。
但這過于復雜且效率低下(兩個加載和索引尋址模式,當然還有額外的指令來設定 RCX)。
只需增加指標,因為這就是您想要回傳的內容。
如果您要一次回圈 1 個位元組,而不是使用 SSE2 一次檢查 16 個位元組,strchr可以簡單如下:
;; BITS 64 is useless unless you're writing a kernel with a mix of 32 and 64-bit code
;; otherwise it only lets you shoot yourself in the foot by putting 64-bit machine code in a 32-bit object file by accident.
global mystrchr
mystrchr:
.loop: ; do {
movzx ecx, byte [rdi] ; c = *p;
cmp cl, sil ; if (c == needle) return p;
je .found
inc rdi ; p
test cl, cl
jnz .loop ; }while(c != 0)
;; fell out of the loop on hitting the 0 terminator without finding a match
xor edi, edi ; p = NULL
; optionally an extra ret here, or just fall through
.found:
mov rax, rdi ; return p
ret
我在字串結尾之前檢查了匹配項,因此我仍然擁有未遞增的指標,而不必在“找到”回傳路徑中遞減它。如果我用 開始回圈inc,我可以使用[rdi - 1]尋址模式,仍然避免使用單獨的計數器。這就是為什么我將哪個分支位于回圈底部的順序與問題中的代碼進行了切換。
由于我們想將字符比較兩次,分別針對 SIL 和零,我將其加載到暫存器中。這可能不會在現代 x86-64 上運行得更快,每個時鐘可以運行 2 個負載以及 2 個分支(只要最多使用其中一個)。
一些英特爾 CPU 可以將微融合和宏融合 cmp reg,mem / jcc為前端的單個加載 比較和分支微指令,至少在記憶體尋址模式簡單而不是索引時。但不是cmp [mem], imm/ jcc,因此我們不會通過單獨加載到暫存器中來為英特爾 CPU 上的前端花費任何額外的微指令。(使用 movzx 來避免寫入部分暫存器的錯誤依賴,例如mov cl, [rdi])
請注意,如果您的呼叫程式也是用匯編語言撰寫的,則回傳多個值很容易,例如狀態和指標(在未找到的情況下,終止 0 可能會很有用)。 許多 C 標準庫字串函式設計得很糟糕,特別是strcpy不能幫助呼叫者避免重做長度查找作業。
特別是在具有 SIMD 的現代 CPU 上,具有顯式長度非常有用:現實世界的strchr實作會檢查對齊情況,或者檢查給定指標是否不在頁面末尾的 16 個位元組內。但memchr不必這樣做,如果大小 >= 16:它可以只movdqu加載和pcmpeqb.
See Is it safe to read past the end of a buffer within the same page on x86 and x64? for details and a link to glibc strlen's hand-written asm. Also Find the first instance of a character using simd for real-world implementations like glibc's using pcmpeqb / pmovmskb. (And maybe pminub for the 0-terminator check to unroll over multiple vectors.)
SSE2 can go about 16x faster than the code in this answer for non-tiny strings. For very large strings, you might hit a memory bottleneck and "only" be about 8x faster.
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/430125.html
上一篇:按鈕單擊的回發問題
