1. 程式人生 > >關於字串比較的一點討論---strcmp與memcmp的效率及實現原理

關於字串比較的一點討論---strcmp與memcmp的效率及實現原理

                要求寫一個比較高效的檔案比較程式,竟然發現memcmp比strcmp要快很多,於是跟蹤除錯,發現它們的實現原理:

intel/strcmp.asm:
    mov edx, dword ptr [esp + 4] ;取第二個引數地址
    mov ecx, dword ptr [esp + 8] ;取第一個引數地址
    test edx, 3  ;edx是第二個引數的地址,這裡即檢驗該地址是否是4的倍數。
                         ;因為如果edx&3!=0,則其最低兩位不為1,所以為4的倍數。這裡有個記憶體地址對齊的問題。
    jne  dopartial ;如果地址不是4的倍數,就跳到dopartial去處理。
dodwords:
    mov  eax, dword ptr [edx]
    cmp  al, byte ptr [ecx]
    jne  donene
    or   al, al  ;看看字串是否結束,這就是strcmp之所以比memcmp慢的地方了。
    je   doneeq  ;如果 al==0,則比較結束
    cmp  ah, byte ptr [ecx + 1]
    jne  donene
    or   ah, ah
    je   doneeq
    shr  eax, 10h ;右移16位
    cmp  al, byte ptr [ecx + 2]
    jne  donene
    or   al, al
    je   doneeq
    cmp  ah, byte ptr [ecx + 3]
    jne  donene
    or   ah, ah
    je   doneeq
    add  ecx, 4
    add  edx, 4
    or   ah, ah
    jne  dodwords
    move edi, edi ;這裡一直大惑不解,不明白為什麼這裡要多出這兩個位元組來
doneeq:
    xor eax, eax   ;比較結果是相等,返回值為0
    ret
    nop ;這裡也一直大惑不解,不明白這裡為什麼要插入一條空指令,感覺和上面的mov edi, edi應該是同一個原因。
donene:
    sbb eax, eax    ;比較結果不相等,這裡也很經典,使用帶借位減法,eax = eax - eax -cf。
    shl eax, 1        ;若不相等處是大於,則 cf == 0,eax == 0,下面加1後eax==1,返回。
    inc eax          ;若不相等處是小於,則 cf == 1,那麼 sbb後eax==-1,補碼為0xFFFFFFFF,左移再加1還是-1
    ret       
    mov edi, edi ;這裡又出來了,不知道為什麼要這麼做,痛苦。
dopartial:
    test edx, 1
    je doword  ;同樣,與1與如果為0,則地址是2的倍數,跳到doword去執行。
    mov al, byte ptr [edx]
    inc edx
    cmp al, byte ptr [ecx]
    jne donene
    inc ecx
    or al, al
    je doneeq
    test edx, 2
    je dodwords
dowords:
    mov ax, word ptr [edx]
    add edx, 2
    cmp al, byte ptr [ecx]
    jne donene
    or al, al
    je doneeq
    cmp ah, byte ptr[ecx + 1]
    jne donene
    or ah, ah
    je doneeq
    add ecx, 2
    jmp dodwords



intel/memcmp.asm

memcmp函式程式碼:
引數堆疊:
offset str1 --- ebp - 4 ------------當前棧頂
offset str2 --- ebp - 8
eax (strlen(str1)的返回值)
memcmp:
    mov eax, dword ptr [esp + 0ch] ;得到memcmp的第三個引數:要比較的個數
    test eax, eax ; 看要比較的位元組數是否為0
    je retnull    ;如果要比較的位元組數為0則直接返回
    mov edx, dword ptr [esp + 4] ;得到memcmp的第一個引數,即offset str1
    push esi
    push edi    ;儲存暫存器值
    mov esi, edx    ;源字串地址,即offset str1
    mov edi, dword ptr [esp + 10h] ;不知何意,
    or edx, edi
    and edx, 3    ;根據strcmp的分析,這裡依然是判斷edx地址是不是4的倍數
    je dwords    ;地址是4的倍數,則跳到dwords去處理
    test eax, 1    ;eax中存的是字串長度。如果地址不是4的倍數,那麼看要比較的位元組數是不是2的倍數。
    je mainloop    ;如果要比較的記憶體位元組數是2的倍數,則轉向mainloop。
    mov cl, byte ptr [esi] ;否則eax==1,比較最後一個位元組
    cmp cl, byte ptr [edi]
    jne not_equal 
    inc esi
    inc edi
    dec eax
    je done
main_loop:
    mov cl, byte ptr [esi]
    mov dl, byte ptr [edi]
    cmp cl, dl
    jne not_equal
    mov cl, byte ptr [esi+1]
    mov dl, byte ptr [edi+1]
    cmp cl, dl
    jne not_equal
    add edi, 2
    add esi, 2
    sub eax, 2
    jne main_loop ;這裡用了jne而不是jmp,太好了,一舉兩得

done:   
    pop edi
    pop esi
retnull:
    ret
dwords:
    mov ecx, eax  ;eax中儲存著字串長度
    and eax, 3
    shr ecx, 2  ;右移兩位,等於除以字串長度除以4, 現在ecx == 100(64h)
    je tail_loop_start ;迴圈移位指令不影響除CF,OF以外的其它位,
            ;故這裡是判斷eax是否是4的倍數,若是(eax & 3 == 0, zf = 1)則跳
    repe cmps dword ptr [esi], dword ptr [edi]
        ;這是一條經典程式碼,cmps為串比較指令
        ;repe/repz:計數相等重複串操作指令功能:
        ; <1>如果cx==0或zf==0(比較的兩數不等),則退出repe/repz
        ; <2>cx = cx - 1
        ; <3>執行其後的串指令
        ; <4>重複<1>--<3>
        ; 對於cmpsr的功能:
        ; 程式碼中是用dword ptr修飾過,所以是雙字比較
        ; 比較完後:edi = edi +/- 4, esi = esi +/- 4
        ; 到底是加還是減,看df位的設定
    je tail_loop_start ;看repe是如何退出的,到底是全部比較完
               ;了都相等退出(則zf==1,je成功跳轉),還是
               ;比較的中途遇到不相等的退出
    mov ecx, dword ptr [esi-4] ;已知是不相等退出的了,現在看具體的大小關係
    mov edx, dword ptr [edi-4] ;所以後退4個位元組比較
    cmp cl, dl
    jne diffrence_in_tail
    cmp ch, dh
    jne diffrence_in_tail
    shr ecx, 10h
    shr edx, 10h
    cmp cl, dl
    jne diffrence_in_tail
    cmp ch, dh
diffrence_in_tail:
    mov eax, 0
not_equal:
    sbb eax, eax
    pop edi
    sbb eax, 0fffffffh
    pop esi
    ret
tail_loop_start:
    test eax, eax ;現在eax是原字串長度模4後的零頭
    je done    ;看看eax是否為0,是則說明比較完成,eax中是零,則返回值是0,相等
    mov edx, dword ptr [esi]
    mov ecx, dword ptr [edi]
    cmp dl, cl
    jne diffrence_in_tail
    dec eax
    je tail_done
    cmp dh, ch
    jne diffrence_in_tail
    dec eax
    je tail_done
    and ecx, 0ff0000h
    and edx, 0ff0000h
    cmp edx, ecx
    jne diffrence_in_tail
    dec eax
tail_doen:
    pop edi
    pop esi
    ret
  
至此,也明白了為什麼這兩個函式會有效率的差別,strcmp比較的字串,而memcmp比較的是記憶體塊,strcmp需要時刻檢查是否遇到了字串結束的 /0 字元,而memcmp則完全不用擔心這個問題。另一個區別是
strcmp在比較四位元組是逐位元組比較,而memcmp是用了字串比較指令,感覺用字串比較指令比用逐位元組比較好,不知道strcmp為什麼比較四位元組時不用。感覺memcmp倒是可以用來實現strncmp函式的功能。
遺留的問題有記憶體位元組對齊的問題,以及兩處mov edi,edi和一處nop指令的問題。交給以後吧。
           

再分享一下我老師大神的人工智慧教程吧。零基礎!通俗易懂!風趣幽默!還帶黃段子!希望你也加入到我們人工智慧的隊伍中來!https://blog.csdn.net/jiangjunshow