【老劉談算法001】這位運算玩的真溜—strlen函數的匯編實現分析
阿新 • • 發佈:2018-06-27
溢出 max 高效率 開發 老劉 移位 中一 model amp 首先掛下代碼,
所以有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。 這方便了再次循環,簡化了代碼,
在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,正好對應真實的字符串長度。
這位老外是真的秀,在追求高效率的時候還不忘精簡一波代碼~
;原函數作者為不知名老外,出處為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
進行邏輯與運算時,結果總是不大於兩個進行運算的數
易得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函數的匯編實現分析