비주얼베이직 소스들을 보다 보면 문자열 비교를 하는 소스가 있는가 하면, 문자열의 길이를 비교하는 경우가 있어요.

예를들어.. 폴더의 존재 유무를 확인하기 위해 Dir 함수를 사용할 때..

[1]
If Len(Dir(폴더경로, vbDirectory)) <> 0 Then
    '폴더가 존재함
End If


[2]
If Dir(폴더경로, vbDirectory) <> "" Then
    '폴더가 존재함
End If


이런 방식의 소스를 많이 봤을꺼에요.

[1]과 같이 코딩을 하는 반면, [2]와 같이 짜시는 분들도 있어요.


이 두 소스의 목적은 같아요.

Dir 함수의 Attributes가 vbDirectory 일때는 해당 폴더의 하위 폴더들을 출력하여요.

따라서 폴더가 존재한다면 반환하는 문자열이 있을꺼에요.

그럼 여기서, 문자열이 존재하지 않다는 "" 과 길이가 0이 아닌가의 비교에서 무엇이 더 빠를까요?
(vbNullString도 아닌 ""를 사용했음)



쓸때없을지 모르지만 왠지 궁금하지 않나요?

분석 GoGo!




Len 함수는 __vbaLenBStr 함수로 들어가고 문자열 비교는 __vbaStrCmp를 사용하네요.

여기 보이는 내용으로는 문자열 비교가 PUSH하는 인자가 1개 더 많다는것을 제외하고는 똑같아요.


그럼 우선 __vbaLenBStr 내부를 살펴볼께요.


아니 이럴수가?

한 눈에 보기에도 굉장히 짧다고 느껴질 정도에요.

734568DF    8B4424 04       MOV EAX,DWORD PTR SS:[ESP+4]
EAX에 인자로 스택에 전달된 문자열 주소를 가져와요.

734568E3    85C0            TEST EAX,EAX
734568E5    74 05           JE SHORT MSVBVM60.734568EC
가져온 문자열 주소가 올바른지 판단하여요.

734568E7    8B40 FC         MOV EAX,DWORD PTR DS:[EAX-4]
문자열의 길이를 구하는 핵심 부분이에요.

EAX는 문자열의 주소를 가리키는데, 그 곳에서 4바이트 앞의 내용을 가져오고 있어요.

그럼 거기 문자열의 길이를 적어두기라도 했다는 걸까요?

네, 맞아요.

비베는 BSTR 체계를 사용하여 유니코드를 쓰는 것이고, 앞부분에 문자열의 길이를 가지고 있어요.
BSTR : http://msdn.microsoft.com/en-us/library/ms221069.aspx


위와 같이 직접 확인해보면 알겠지만, "Documents and Settings" 앞에 0x0000002C가 있어요.

물론 유니코드이기 때문에 한 글자가 2바이트이므로 2배한 0x2C가 있어요.

734568EA    D1E8            SHR EAX,1
끝으로 반으로 나눠서 리턴해요.



이미 승부는 결정났어요.

문자열의 비교보다 Len함수가 더 빠르네요.

그래도 이왕 시작한거 문자열 비교도 리버싱을 해보겠어요.




__vbaStrCmp를 호출할때 인자 2개를 넘기고 있어요.

004016E2      50                 PUSH EAX
004016E3      68 9C134000    PUSH 0040139C
EAX는 rtcDir에서 반환된 문자열이고,
0040139C는 비교할 문자열이에요.
제 예제에서 EAX는 "Documents and Settings"에요.


다음은 __vbaStrCmp 내부에요.

734593DA    FF7424 08       PUSH DWORD PTR SS:[ESP+8]
734593DE    FF7424 08       PUSH DWORD PTR SS:[ESP+8]
734593E2    6A 00             PUSH 0
첫번째 ESP+8은 함수 호출전의 EAX에요. EAX를 PUSH하고 ESP는 감소하여요.
따라서 두번째 ESP+8은 함수 호출전의 0040139C와 같아요.

734593E4    E8 44E6FFFF   CALL MSVBVM60.__vbaStrComp
__vbaStrComp 함수를 호출해요.


이제 __vbaStrComp 내부에요.


73457A2D    837C24 04 02    CMP DWORD PTR SS:[ESP+4],2
73457A32    75 07              JNZ SHORT MSVBVM60.73457A3B
ESP+4는 __vbaStrComp 호출전에 PUSH 0한 부분이에요.
0이 있을 것이고, 2와 CMP하고 있어요.
오른쪽이 더 크므로 ZF는 0, CF는 1이 되어요. 따라서 점프하여요.

73457A3B    68 01000300     PUSH 30001
73457A40     FF7424 08       PUSH DWORD PTR SS:[ESP+8]
73457A44     FF7424 10       PUSH DWORD PTR SS:[ESP+10]
73457A48     FF7424 18       PUSH DWORD PTR SS:[ESP+18]
30001, 0, "", "Documents and Settings" 를 PUSH 하네요.
모두 위에서 부터 내려오는 것들이에요.

73457A4C    FF15 38EF4773   CALL DWORD PTR DS:[7347EF38]             ; OLEAUT32.VarBstrCmp
또 다시 다른 함수 VarBstrCmp를 호출하는데, Oleaut32.dll에 있는 API에요.
이 함수는 MSDN에도 설명되어 있어요.
VarBstrCmp : http://msdn.microsoft.com/en-us/library/ms221038.aspx



끝으로 VarBstrCmp 내부에요.
전형적인 함수의 시작부분을 지나고..

770EA773    8B75 08            MOV ESI,DWORD PTR SS:[EBP+8]
...
770EA777    56                   PUSH ESI
770EA778    E8 0BA5FEFF     CALL OLEAUT32.SysStringByteLen
...
770EA780    8BD8                MOV EBX,EAX
ESI에는 __vbaStrCmp에서 첫번째로 PUSH한 녀석이 쭉~ 올라왔어요. 제 예제에서는 "Documents and Settings"이지요.
그리고 SysStringByteLen를 호출해요.
이 함수는 위에서 봤던 __vbaLenBStr 함수와 매우 비슷하게 동작하는 문자열 길이 반환 함수에요.
그리고 반환된 길이를 EBS에 옮겨요.

770EA77D    8B7D 0C         MOV EDI,DWORD PTR SS:[EBP+C]             ; Test1_2.0040139C
...
770EA782    57                  PUSH EDI
...
770EA786    E8 FDA4FEFF   CALL OLEAUT32.SysStringByteLen
마찬가지로 비교하는 문자열도 길이를 가져오네요.

770EA78B    3BD8            CMP EBX,EAX
...
770EA790    72 02            JB SHORT OLEAUT32.770EA794
두 길이를 비교! 예제에서는 EBX가 더 크므로 ZF, CF모두 0이 되어요.
따라서 점프하지 않아요.

770EA783    895D 08          MOV DWORD PTR SS:[EBP+8],EBX
...
770EA78D    8945 0C         MOV DWORD PTR SS:[EBP+C],EAX
그리고 위와 같이 인자로 전달된 스택을 반환된 길이로 바꿔버리는 부분이 중간중간 있었어요.

770EA794    8B4D 10         MOV ECX,DWORD PTR SS:[EBP+10]
770EA797    85C9             TEST ECX,ECX
770EA799    75 24             JNZ SHORT OLEAUT32.770EA7BF
점프 후에 행하는 부분이에요.
ECX에 EBP+10을 옮기는데, EBP+10은 __vbaStrCmp에서 부터 인자로 넘기던 0 이에요.
MSDN 설명을 읽어보면 Unicode인지 Ansi인지 확인하는 인자 같네요.


아무튼 이런식으로 비교를 하다 보면 구했던 길이로 비교하는 부분이 나와요.

물론 길이가 다르기 때문에 EAX에 2를 넣고 함수를 종료하네요.



이어서
__vbaStrComp에서는 EAX를 1 감소시키고 함수를 종료해요.



아무튼간에 결론은 길이 비교가 훨씬 빨라요.

다음엔 파싱할때 Instr과 Split의 속도 차이를 알아보아요.

저작자 표시 비영리 변경 금지
Posted by Gogil

댓글을 달아 주세요

  1. 빗방울 2011/02/26 16:17  댓글주소  수정/삭제  댓글쓰기

    Len 과 LenB 의 속도 차이는?

  2. ^^ 2011/03/02 15:19  댓글주소  수정/삭제  댓글쓰기

    마침 해당 부분 손보고 있었는데 정보 ㄳ요~ ^^

  3. NTluna 2012/09/22 16:05  댓글주소  수정/삭제  댓글쓰기

    어서 InStr과 Split의 차이를 보여주세요..!