follow my heart...
分类:
2006-08-29 09:57:48
记得原来给aiirii(ari)胡诌了一通PChar和string,一直感觉心里有愧,现在好好说一说这个知识点。
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到目的字符串,
最后清理临时字符串。
好了,我又一次胡诌完毕了。请大家执正!
这是我写的,leeon你看看怎么样,我自己总结的:
关于Delphi 中 String (又叫 LongString、AnsiString) 与传统的 PChar的区别
1. string 是 Delphi 编译器内在支持的(predefined or built-in),是Delphi 的一个基本数据类型,而 PChar 只是一个指向零终止字符串的指针;
2. String 所存字符串是在堆(Heap)分配内存的,String
变量实际上是指向零终止字符串的指针,与此同时它还具有引用计数(reference
count)功能,并且自身保存字符串长度,当引用计数为零时,自动释放所占用的空间。
3.将 string 赋值给另一个string,只是一个简单的指针赋值,不产生 copy 动作,只是增加string的引用计数;
4. 将一个 PChar 变量类型赋值给一个 string 变量类型会产生真正的 Copy 动作,即将 PChar 所指向的字符串整个copy到为string分配的内存中;
5. 将 string 赋值给一个 PChar 变量类型,只是简单地将string的指针值赋值给PChar 变量类型,而string
的引用计数并不因此操作而发生变化,因为这种情况 PChar 会对 string 产生依赖,当string
的引用计数为零自动释放内存空间后,PChar很可能指向一个无效的内存地址,在你的程序你必须小心对付这种情况。
6. 对 PChar 的操作速度要远远高于对 string 操作的速度,但 PChar 是一种落后的管理字符串的方式,而 string 则以高效的管理而胜出,PChar 它的存在只是为了兼容早期的类型和操作系统(调用 API时会经常用到),建议平常使用 string。
你的那个demo程序我认为设计得非常糟糕,基本上说不清问题:
我设计的Demo程序:
procedure TForm1.Button1Click(Sender: TObject);
var
P: PChar;
S1, S2: string;
begin
S1 := '1234567ABCDE';
S2 := S1;
P := PChar(S1);
ShowMessage(IntToStr(SizeOf(S1))); //获得 S1 变量的大小
ShowMessage(IntToStr(integer(S1))); //获得 S1 指向的字符串的指针地址
ShowMessage(IntToStr(integer(P))); //获得 P 指向的字符串的指针地址
ShowMessage(IntToStr(integer(S2))); //获得 S2 指向的字符串的指针地址
S1 := P;
ShowMessage(IntToStr(integer(S1)));
ShowMessage(IntToStr(integer(P)));
ShowMessage(IntToStr(integer(S2)));
end;
分析代码要点:
S2 := S1 验证我总结的第3点
P := PChar(S1) 验证我总结的第5点
S1 := P 验证我总结的第4点
我这里按顺序显示的结果(您的结果可能与此不同,但不妨碍分析)
4 <= S1 变量的大小
4530660 <= S1 指向的字符串的指针地址
4530660 <= P 指向的字符串的指针地址
4530660 <= S2 指向的字符串的指针地址
9780484 <= S1 指向的字符串的指针地址 (地址发生变化)
4530660 <= P 指向的字符串的指针地址
4530660 <= S2 指向的字符串的指针地址
关于分析它的汇编代码,我认为没什么必要,先搞清楚这些基本的操作!
如果不考虑太多的话,我们采用简单的方法:
1:pchar to string:
ansistring a;
pchar b;
a=string(b);
2: string to pchar
ansistring a;
pchar b;
b=pchar(a);
不过类型不太安全的。