AnsiString VS Pchar

AnsiString?PChar?賦值?轉換?

AnsiString,Delphi開發人員指南這麼解釋的,AnsiString就是指向堆中字符串結構的指針,顯示了AnsiString的分配情況。
幫助上這麼解釋:長字符串保存有成員數量,PChar沒有,長字符串(相對於ShortString類型來說,這裏就是AnsiString)是Null結尾的,並且包含了引用記數,PChar就是一個簡單的Null結尾的字符串。AnsiString之間的賦值是賦值數據,而PChar賦值是改變指針的指向。

同樣存儲LIKE,兩種類型差異:

string簡圖:
--------------------------------------------------------
| 引用計數位 | 長度位 |  L | I | K | E | #0  |
--------------------------------------------------------

PChar簡圖:
-------------------------
|  L | I | K | E | #0  |
-------------------------

而string的指針通常指向數據位第一個,訪問前面的引用計數和長度位的時候word ptr [eax-$04], dword ptr [eax-$08],這一類的代碼。

它們之間的轉換,Delphi開發人員指南里面是這麼寫的:不需要用StrPas和StrPCopy去來回轉換string和PChar類型。我們在前面講過,可以把AnsiString強制轉化成PChar。在想把PChar轉換成AnsiString時,可以直接賦值:StringVar := PCharVar;

StrPas的原型是什麼?我悄悄地告訴你啊,不要告訴別人哦!:〉
function StrPas(const Str: PChar): string;
begin
  Result := Str;
end;
哈哈,搞笑吧。Borland弄了這麼一個東西…… -_-bb
其實是爲了兼容而設立的。

StrPCopy相對來講就比較複雜了,是把string強制轉化成PChar之後按照string的長度進行PChar之間複製。

function StrPCopy(Dest: PChar; const Source: string): PChar;
begin
  Result := StrLCopy(Dest, PChar(Source), Length(Source));
end;

而StrLCopy類似於C語言裏面的strcpy這種用來複制內容的函數,所以儘量用StrLCopy來給PChar賦值,
因爲會保護現場,用賦值號,就改變了指針的指向了。

有關Delphi開發人員指南里面的介紹,我寫了這麼一些代碼來驗證一下:
var
  P: PChar;
  S: string;
begin
  S := '1234567ABCDE';
  P := PChar(S); 
  S := P;
  ShowMessage(S);
  ShowMessage(P);
  ShowMessage(IntToStr(SizeOf(S)));
end;

當執行到S := '1234567ABCDE';時,彙編代碼如下:

第一句:S := '1234567ABCDE';
0045B130 8D45FC           lea eax,[ebp-$04]
0045B133 BAB0B14500       mov edx,$0045b1b0
0045B140 E8E791FAFF       call @LStrLAsg

LStrLAsg就是字符串賦值地內建函數。正驗證了幫助裏面的那句string之間是數據Copy。

第二句:P := PChar(S);
0045B13D 8B45FC           mov eax,[ebp-$04]
0045B140 E8FF95FAFF       call @LStrToPChar
0045B145 8BD8             mov ebx,eax

進入這一句了,LStrToPChar是不是很容易理解呢,string轉化爲PChar。然後簡單的賦地址,指針改變指向。

好了該由PChar轉換到string了,這個是一個重點。

第三句:S := P;
0045B147 8D45FC           lea eax,[ebp-$04]
0045B14A 8BD3             mov edx,ebx
0045B14C E83393FAFF       call @LStrFromPChar

LStrFromPChar是由PChar轉換string的一個內建函數。

進入LStrFromPChar的代碼
@LStrFromPChar
00404484 31C9             xor ecx,ecx
00404486 85D2             test edx,edx
00404488 7421             jz +$21
0040448A 52               push edx
0040448B 3A0A             cmp cl,[edx]
0040448D 7417             jz +$17
0040448F 3A4A01           cmp cl,[edx+$01]
00404492 7411             jz +$11
00404494 3A4A02           cmp cl,[edx+$02]
00404497 740B             jz +$0b
00404499 3A4A03           cmp cl,[edx+$03]
0040449C 7405             jz +$05
0040449E 83C204           add edx,$04
004044A1 EBE8             jmp -$18     //循環得到字符串長度。
004044A3 42               inc edx
004044A4 42               inc edx
004044A5 42               inc edx
004044A6 89D1             mov ecx,edx
004044A8 5A               pop edx
004044A9 29D1             sub ecx,edx //到這裏得到了PChar的長度。
004044AB E9D4FEFFFF       jmp @LStrFromPCharLen
004044B0 C3               ret

然後處理成string的長度的轉化:

@LStrFromPCharLen
00404384 53               push ebx
00404385 56               push esi
00404386 57               push edi
00404387 89C3             mov ebx,eax
00404389 89D6             mov esi,edx
0040438B 89CF             mov edi,ecx
0040438D 89F8             mov eax,edi
0040438F E8C4FFFFFF       call @NewAnsiString

這裏調用了NewAnsiString來生成一個新的標準字符串。
我們跳過來順道看看string到底怎麼生成的。string生成儘管是自動的,
其實和手工聲稱PChar差不多,不同的是比PChar多的長度位和引用計數的初始化。

@NewAnsiString
00404358 85C0             test eax,eax
0040435A 7E24             jle +$24
0040435C 50               push eax
0040435D 83C00A           add eax,$0a
00404360 83E0FE           and eax,-$02
00404363 50               push eax
00404364 E8B3E3FFFF       call @GetMem  //得到字符串長度後申請內存。
00404369 5A               pop edx
0040436A 66C74402FE0000   mov word ptr [edx+eax-$02],$0000
00404371 83C008           add eax,$08 
00404374 5A               pop edx
00404375 8950FC           mov [eax-$04],edx       //這裏應該就長度位
00404378 C740F801000000   mov [eax-$08],$00000001 //這裏是引用記數位
0040437F C3               ret
00404380 31C0             xor eax,eax
00404382 C3               ret
00404383 90               nop

繼續LStrFromPCharLen下面的代碼。
00404394 89F9             mov ecx,edi
00404396 89C7             mov edi,eax
00404398 85F6             test esi,esi
0040439A 7409             jz +$09
0040439C 89C2             mov edx,eax
0040439E 89F0             mov eax,esi
004043A0 E873E5FFFF       call Move      //然後把這個新的字符串的數據移到需要的字符串。
004043A5 89D8             mov eax,ebx
004043A7 E8E8FEFFFF       call @LStrClr  //清理NewAnsiString生成的臨時字符串
004043AC 893B             mov [ebx],edi
004043AE 5F               pop edi
004043AF 5E               pop esi
004043B0 5B               pop ebx
004043B1 C3               ret

ShowMessage直接輸入string就是直接調用。
ShowMessage(S);
0045B159 8B45FC           mov eax,[ebp-$04]
0045B15C E8638EFDFF       call ShowMessage

而輸入PChar,嗬嗬,又調用了上面說到的LStrFromPChar然後纔是調用ShowMessage,
其他的也是如此,如果輸入參數沒有轉換,而是輸入了PChar,那編譯器會這麼處理。
ShowMessage(P);
0045B161 8D45F8           lea eax,[ebp-$08]
0045B164 8BD3             mov edx,ebx
0045B166 E81993FAFF       call @LStrFromPChar
0045B16B 8B45F8           mov eax,[ebp-$08]
0045B16E E8518EFDFF       call ShowMessage

最後一句:ShowMessage(IntToStr(SizeOf(S)));
如果沒有差錯的話,就是4,一個指針的長度。

好了,一切都清晰了。
S := S;
使用了LStrLAsg內建函數來賦值。

P := P;
地址賦值。

P := PChar(S);
調用LStrToPChar轉換爲PChar之後地址賦值。

S := P;

這個最最麻煩,
調用LStrFromPChar來轉化。
代碼中首先取得PChar的長度,
然後調用LStrFromPCharLen用string的長度賦值,
代碼中首先使用NewAnsiString在堆中生成一個臨時字符串,
其次調用Move,Copy到目的字符串,
最後清理臨時字符串。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章