1. 程式人生 > >【老劉談算法001】這位運算玩的真溜—strlen函數的匯編實現分析

【老劉談算法001】這位運算玩的真溜—strlen函數的匯編實現分析

溢出 max 高效率 開發 老劉 移位 中一 model amp

首先掛下代碼,

;原函數作者為不知名老外,出處為MASM32開發包,在此表示感謝。
;中文註釋修改&添加 By 老劉。

    .486
    .model flat, stdcall
    option casemap :none

    .code

OPTION PROLOGUE:NONE 
OPTION EPILOGUE:NONE 

align 4

StrLen proc item:DWORD

    mov     eax, [esp+4]            ;獲得參數item,即字符串指針
    lea     edx, [eax+3]            ;edx=指針+3
    push    ebp                     ;備份ebp edi
    push    edi
    mov     ebp, 80808080h

  @@:     
  REPEAT 3
    mov     edi, [eax]              ;edi=讀4個字節
    add     eax, 4                  ;字符串指針指到下4個字節起始,即+4
    lea     ecx, [edi-01010101h]    ;ecx=四字節每個字節-1
    not     edi                     ;edi=四字節邏輯取反
    and     ecx, edi                ;ecx=取反後的每個字節和沒取反後-1的每個字節進行邏輯與
    and     ecx, ebp                ;判斷每個字節的二進制第8位是否為1,為1則說明原字節=0
    jnz     nxt                     ;如果出現了一個null異端,跳到nxt繼續判斷
  ENDM

    mov     edi, [eax]              ;和上面的一樣
    add     eax, 4                  
    lea     ecx, [edi-01010101h]    
    not     edi                     
    and     ecx, edi                
    and     ecx, ebp
    jz      @B                      ;如果沒有異端,回去上面循環,有的話都不用跳了,下面就是nxt

  nxt:
    test    ecx, 00008080h          ;測試null是否在前2個字節中
    jnz     @F
    shr     ecx, 16                 ;不在前2字節,ecx右移16個二進制位,即右移2字節,使原來的第3、4字節移位到1、2字節處。
    add     eax, 2                  ;字符串指針+2
  @@:
    shl     cl, 1                   ;第一個字節邏輯左移1位,如果第一個字節為Null,則CF=1,否則即說明第二個字節為Null
    sbb     eax, edx                ;eax=eax-edx-CF=eax-(item+3)-CF=字符串長度
    pop     edi
    pop     ebp

    ret     4

StrLen endp

OPTION PROLOGUE:PrologueDef 
OPTION EPILOGUE:EpilogueDef 

end

作者的底層知識非常牢固,位運算運用的爐火純青,
代碼算不上精簡,但非常高效,
為了達到上述目的,其代碼人類直接不好理解,讓我們將幾處難理解的地方細細分析道來。

一、高效 —— 一次判斷4個字節
對應Line25~~42,
這段代碼運用位運算,在寄存器中一次性判斷4個Byte內有無Null。
我們按一個字節進行分析,用數學語言轉換代碼,下述的十進制數均為UByte類型。
令a=該Byte的無符號值,
令f(x)為邏輯非運算的對應函數,
有f(x)=255-x
b=a-1
此時因為溢出,有b=a-1(a∈[1,255])或b=255(a=0)
我們將其設為函數,有b=g(a)
c=f(a)
d=b and c
進行邏輯與運算時,結果總是不大於兩個進行運算的數

所以有d<=b和c中較小的那一個
易得a∈[1,127]時,d<=g(a),此時g(a)max=g(127)=126,
a∈[128,255]時,d<=f(a),f(a)max=f(128)=127,
a=0時,d = 255 and 255 = 255,
e=d and 0x80
a∈[1,127]時,d<=126,此時∵d的第8二進制位上定為0,e=0
同理,a∈[128,255],e=0
a=0,e=0x80
即a不為0時,e=0,a為0時,e=0x80。

二、拒絕浪費寄存器——字符串長度的最終計算
對應Line:20、28、37、48、50、51
作者讀取完需判斷的字節後,不管3721,先將指針+4(28、37行)

這方便了再次循環,簡化了代碼,
在48行將指針+2,
50行時令CF=指針所指字節第8位
51行則是最終計算,
由一可知,當Byte為null時,判斷下來第8二進制位=1
即當指針所指字節不為null,CF=1
51行得到的結果=當前指針-字符串起始指針-3-1
當Byte不為null時,由其他語句得知下一個byte肯定為null,
此時第8二進制位=0,CF=0
51行得到的結果則少減了1,正好對應真實的字符串長度。
這位老外是真的秀,在追求高效率的時候還不忘精簡一波代碼~

該代碼還有局限性,當出現雙字節字符時就會判斷成2個長度
不過對於一個用英文編程的老外來說,也可以理解,
代碼質量可以說是很高了,只是讓分析它的人非常蛋痛(ˉ▽ˉ;)...
可能這就是“最高效率和可讀性不可兼得”吧!

【老劉談算法001】這位運算玩的真溜—strlen函數的匯編實現分析