在Delphi中字符串类型为String,有ShortString, AnsiString, WideString三种
类型 最大长度 占用内存
ShortString 255 2-256 向后兼容
AnsiString 大约2^31 4字节-2G 8位(ANSI)字符
WideString 大约2^30 4字节-2G 多用户服务和多语言程序
缺省的编译选项下,编译器认为String就是AnsiString字符串(可以使用$H编译开关
来进行修改)。
一,AnsiString的内部格式
AnsiString能够根据需要动态的分配字符串空间,并且具有自动回收的功能。它以null
字符结束,所以可以通过PCahr转换成为与WindowsAPI兼容的字符串。AnsiString类型
在内存中实际上是以以下形式存在的:
+++++++++++++++++++++++++++++++++
| 4字节分配的大小 |
+++++++++++++++++++++++++++++++++
| 4字节引用计数 |
+++++++++++++++++++++++++++++++++
| 4字节长度 |
+++++++++++++++++++++++++++++++++
| ...不定长度字符串... |
+++++++++++++++++++++++++++++++++
| #0 |
+++++++++
AnsiString实际上是指向字符串结构的指针。例如:
var
str: AnsiString;
begin
str := 'abcd';
Memo1.Lines.Add('AnsiString Size: ' + IntToStr(SizeOf(AnsiString)));
Memo1.Lines.Add('str Size: ' + IntToStr(SizeOf(str)));
end;
程序的输出结果总是为4。也就是说AnsiString类型的变量实际上是一个指针。
因为Borland公司保留了在Delphi以后版本中修改字符串内部格式的权利,所以在程序
中要避免使用依赖于字符串内部格式的代码。
下面一个例子说明AnsiString在内存中的内部格式:
var
str: String;
procedure TForm1.Button1Click(Sender: TObject);
begin
str := 'abcd';
Memo1.Lines.Text :=
'分配大小: ' + IntToStr(PInteger(Integer(str) - 12)^) + #13#10 +
'引用计数: ' + IntToStr(PInteger(Integer(str) - 8)^) + #13#10 +
'字串长度: ' + IntToStr(PInteger(Integer(str) - 4)^) + #13#10 +
'字串: ' + PChar(Integer(str));
end;
输出结果:
分配大小: 22
引用计数: 1
字串长度: 4
字串: abcd
是字串长度+17,不知道是不是分配大小4 + 引用计数4 + 字串长度4 + #0结束符1
+ 字符串指针4 = 17(这是我猜的)
另外还有一个我不明白的问题就是,如果str定义为全局变量,或者TForm1的成员的
话输出结果和上面的一样,但是如果把str在函数内部定义为局部变量的话,输出的
结果就不太合理。
二,引用计数
AnsiString字符串具有引用计数功能,也就是说可以几个字符串指向同一内存地址
例如:
var
str1, str2: String;
procedure TForm1.Button1Click(Sender: TObject);
begin
str1 := 'abcd';
str2 := str1;
Memo1.Lines.Add('str1 Address: ' + IntToStr(Integer(str1)));
Memo1.Lines.Add('str2 Address: ' + IntToStr(Integer(str2)));
end;
程序执行结果:
str1 Address: 13449996
str2 Address: 13449996
可见Delphi中复制AnsiString字符串只是复制了指针,将两个字符串指向同一内存
空间,而不是将实际的字符串复制一遍。这样复制字符串的效率显然要高一些。
通过查看引用计数可以更清楚这一点:
var
str1, str2: String;
procedure TForm1.Button1Click(Sender: TObject);
begin
str1 := 'abcd';
Memo1.Lines.Add('str1 := ''abcd''');
str2 := str1;
Memo1.Lines.Add('str2 := str1');
Memo1.Lines.Add(
'str1 Address: ' + IntToStr(Integer(str1)) + #13#10 +
'str1 Reference: ' + IntToStr(PInteger(Integer(str1) - 8)^) + #13#10 +
'str1 Value: ' + str1 + #13#10 +
'str2 Address: ' + IntToStr(Integer(str2)) + #13#10 +
'str2 Reference: ' + IntToStr(PInteger(Integer(str2) - 8)^) + #13#10 +
'str2 Value: ' + str2 + #13#10
);
str2 := '1234';
Memo1.Lines.Add('str2 := ''1234''');
Memo1.Lines.Add(
'str1 Address: ' + IntToStr(Integer(str1)) + #13#10 +
'str1 Reference: ' + IntToStr(PInteger(Integer(str1) - 8)^) + #13#10 +
'str1 Value: ' + str1 + #13#10 +
'str2 Address: ' + IntToStr(Integer(str2)) + #13#10 +
'str2 Reference: ' + IntToStr(PInteger(Integer(str2) - 8)^) + #13#10 +
'str2 Value: ' + str2 + #13#10
);
end;
程序执行结果:
str1 := 'abcd'
str2 := str1
str1 Address: 13449996
str1 Reference: 2
str1 Value: abcd
str2 Address: 13449996
str2 Reference: 2
str2 Value: abcd
str2 := '1234'
str1 Address: 13449996
str1 Reference: 1
str1 Value: abcd
str2 Address: 13450232
str2 Reference: 1
str2 Value: 1234
由此可见,两个字符串指向同一个内存空间的时候,字符串的引用计数为2。当为其
中一个字符串赋值的时候,Delphi使用Copy-on-write技术,如果发现其引用计数
大于1,则重新开辟一块内存存放新的字符串,同时将原来的字符串引用计数-1。
几个问题没想明白
1。AnsiString里面那个分配空间到底是什么意思,他和字符串长度的关系
是字符串长度+17,不知道是不是我前面猜测的那个原因
2。str放到TForm1的成员里面或者做全局变量的时候,验证结果没有问题
把str放到局部变量里面的话,验证的结果就有问题,什么原因
------------------------------------------------------------------
看了半天,
1、我理解的那个分配空间的意思的就是编译器为字符串
的变化所分配的内存空间,因为改变一个字符串肯定
需要额外的空间。它的大小和字符串长度有一定的关
系,字符串越长,它所需要的空间也就越大。
我做了一个测试,发现它的最小值是18,至少比字符
串变量的值大15,当字符串变大时,它的大小每次以
4递增。
测试的代码如下:
program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils;
function StringStatus (const Str: string): string;
begin
Result := '内存地址: ' + IntToStr (Integer (Str)) +
',内存分配大小:'+IntToStr(PInteger(Integer(str) - 12)^)+
', 长度: ' + IntToStr (Length (Str)) +
', 引用记数: ' + IntToStr (PInteger (Integer (Str) - 8)^);
end;
var
str:String;
i:Integer;
begin
str:='';
for i:=0 to 100 do
begin
str:=str+'1';
Writeln(StringStatus(str));
end;
readln;
end.
得到的结果如下:
内存地址: 13044040,内存分配大小:18, 长度: 1, 引用记数: 1
内存地址: 13044040,内存分配大小:18, 长度: 2, 引用记数: 1
内存地址: 13044040,内存分配大小:18, 长度: 3, 引用记数: 1
内存地址: 13044040,内存分配大小:22, 长度: 4, 引用记数: 1
内存地址: 13044040,内存分配大小:22, 长度: 5, 引用记数: 1
内存地址: 13044040,内存分配大小:22, 长度: 6, 引用记数: 1
内存地址: 13044040,内存分配大小:22, 长度: 7, 引用记数: 1
内存地址: 13044040,内存分配大小:26, 长度: 8, 引用记数: 1
内存地址: 13044040,内存分配大小:26, 长度: 9, 引用记数: 1
内存地址: 13044040,内存分配大小:26, 长度: 10, 引用记数: 1
内存地址: 13044040,内存分配大小:26, 长度: 11, 引用记数: 1
内存地址: 13044040,内存分配大小:30, 长度: 12, 引用记数: 1
内存地址: 13044040,内存分配大小:30, 长度: 13, 引用记数: 1
内存地址: 13044040,内存分配大小:30, 长度: 14, 引用记数: 1
内存地址: 13044040,内存分配大小:30, 长度: 15, 引用记数: 1
内存地址: 13044040,内存分配大小:34, 长度: 16, 引用记数: 1
内存地址: 13044040,内存分配大小:34, 长度: 17, 引用记数: 1
内存地址: 13044040,内存分配大小:34, 长度: 18, 引用记数: 1
内存地址: 13044040,内存分配大小:34, 长度: 19, 引用记数: 1
内存地址: 13044040,内存分配大小:38, 长度: 20, 引用记数: 1
内存地址: 13044040,内存分配大小:38, 长度: 21, 引用记数: 1
内存地址: 13044040,内存分配大小:38, 长度: 22, 引用记数: 1
内存地址: 13044040,内存分配大小:38, 长度: 23, 引用记数: 1
内存地址: 13044040,内存分配大小:42, 长度: 24, 引用记数: 1
内存地址: 13044040,内存分配大小:42, 长度: 25, 引用记数: 1
内存地址: 13044040,内存分配大小:42, 长度: 26, 引用记数: 1
内存地址: 13044040,内存分配大小:42, 长度: 27, 引用记数: 1
内存地址: 13044040,内存分配大小:46, 长度: 28, 引用记数: 1
内存地址: 13044040,内存分配大小:46, 长度: 29, 引用记数: 1
内存地址: 13044040,内存分配大小:46, 长度: 30, 引用记数: 1
内存地址: 13044040,内存分配大小:46, 长度: 31, 引用记数: 1
内存地址: 13044040,内存分配大小:50, 长度: 32, 引用记数: 1
内存地址: 13044040,内存分配大小:50, 长度: 33, 引用记数: 1
内存地址: 13044040,内存分配大小:50, 长度: 34, 引用记数: 1
内存地址: 13044040,内存分配大小:50, 长度: 35, 引用记数: 1
内存地址: 13044040,内存分配大小:54, 长度: 36, 引用记数: 1
内存地址: 13044040,内存分配大小:54, 长度: 37, 引用记数: 1
内存地址: 13044040,内存分配大小:54, 长度: 38, 引用记数: 1
内存地址: 13044040,内存分配大小:54, 长度: 39, 引用记数: 1
内存地址: 13044040,内存分配大小:58, 长度: 40, 引用记数: 1
内存地址: 13044040,内存分配大小:58, 长度: 41, 引用记数: 1
内存地址: 13044040,内存分配大小:58, 长度: 42, 引用记数: 1
内存地址: 13044040,内存分配大小:58, 长度: 43, 引用记数: 1
内存地址: 13044040,内存分配大小:62, 长度: 44, 引用记数: 1
内存地址: 13044040,内存分配大小:62, 长度: 45, 引用记数: 1
内存地址: 13044040,内存分配大小:62, 长度: 46, 引用记数: 1
内存地址: 13044040,内存分配大小:62, 长度: 47, 引用记数: 1
内存地址: 13044040,内存分配大小:66, 长度: 48, 引用记数: 1
内存地址: 13044040,内存分配大小:66, 长度: 49, 引用记数: 1
2、对于局部变量,我得到了一样的结果,代码如下:
program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils;
function StringStatus(const Str: string): string;
begin
Result := '内存地址: ' + IntToStr(Integer(Str)) +
',内存分配大小:' + IntToStr(PInteger(Integer(str) - 12)^) +
', 长度: ' + IntToStr(Length(Str)) +
', 引用记数: ' + IntToStr(PInteger(Integer(Str) - 8)^);
end;
procedure ShowStrStatus;
var
str: string;
i: Integer;
begin
str := '';
for i := 0 to 100 do
begin
str := str + '1';
Writeln(StringStatus(str));
end;
end;
begin
ShowStrStatus;
readln;
end.
得到的结果如下:
内存地址: 13044040,内存分配大小:18, 长度: 1, 引用记数: 1
内存地址: 13044040,内存分配大小:18, 长度: 2, 引用记数: 1
内存地址: 13044040,内存分配大小:18, 长度: 3, 引用记数: 1
内存地址: 13044040,内存分配大小:22, 长度: 4, 引用记数: 1
内存地址: 13044040,内存分配大小:22, 长度: 5, 引用记数: 1
内存地址: 13044040,内存分配大小:22, 长度: 6, 引用记数: 1
内存地址: 13044040,内存分配大小:22, 长度: 7, 引用记数: 1
内存地址: 13044040,内存分配大小:26, 长度: 8, 引用记数: 1
内存地址: 13044040,内存分配大小:26, 长度: 9, 引用记数: 1
内存地址: 13044040,内存分配大小:26, 长度: 10, 引用记数: 1
内存地址: 13044040,内存分配大小:26, 长度: 11, 引用记数: 1
内存地址: 13044040,内存分配大小:30, 长度: 12, 引用记数: 1
内存地址: 13044040,内存分配大小:30, 长度: 13, 引用记数: 1
内存地址: 13044040,内存分配大小:30, 长度: 14, 引用记数: 1
内存地址: 13044040,内存分配大小:30, 长度: 15, 引用记数: 1
内存地址: 13044040,内存分配大小:34, 长度: 16, 引用记数: 1
内存地址: 13044040,内存分配大小:34, 长度: 17, 引用记数: 1
内存地址: 13044040,内存分配大小:34, 长度: 18, 引用记数: 1
内存地址: 13044040,内存分配大小:34, 长度: 19, 引用记数: 1
内存地址: 13044040,内存分配大小:38, 长度: 20, 引用记数: 1
内存地址: 13044040,内存分配大小:38, 长度: 21, 引用记数: 1
内存地址: 13044040,内存分配大小:38, 长度: 22, 引用记数: 1
内存地址: 13044040,内存分配大小:38, 长度: 23, 引用记数: 1
内存地址: 13044040,内存分配大小:42, 长度: 24, 引用记数: 1
内存地址: 13044040,内存分配大小:42, 长度: 25, 引用记数: 1
内存地址: 13044040,内存分配大小:42, 长度: 26, 引用记数: 1
内存地址: 13044040,内存分配大小:42, 长度: 27, 引用记数: 1
内存地址: 13044040,内存分配大小:46, 长度: 28, 引用记数: 1
内存地址: 13044040,内存分配大小:46, 长度: 29, 引用记数: 1
内存地址: 13044040,内存分配大小:46, 长度: 30, 引用记数: 1
内存地址: 13044040,内存分配大小:46, 长度: 31, 引用记数: 1
内存地址: 13044040,内存分配大小:50, 长度: 32, 引用记数: 1
内存地址: 13044040,内存分配大小:50, 长度: 33, 引用记数: 1
内存地址: 13044040,内存分配大小:50, 长度: 34, 引用记数: 1
内存地址: 13044040,内存分配大小:50, 长度: 35, 引用记数: 1
内存地址: 13044040,内存分配大小:54, 长度: 36, 引用记数: 1
内存地址: 13044040,内存分配大小:54, 长度: 37, 引用记数: 1
内存地址: 13044040,内存分配大小:54, 长度: 38, 引用记数: 1
内存地址: 13044040,内存分配大小:54, 长度: 39, 引用记数: 1
内存地址: 13044040,内存分配大小:58, 长度: 40, 引用记数: 1
内存地址: 13044040,内存分配大小:58, 长度: 41, 引用记数: 1
内存地址: 13044040,内存分配大小:58, 长度: 42, 引用记数: 1
内存地址: 13044040,内存分配大小:58, 长度: 43, 引用记数: 1
内存地址: 13044040,内存分配大小:62, 长度: 44, 引用记数: 1
内存地址: 13044040,内存分配大小:62, 长度: 45, 引用记数: 1
内存地址: 13044040,内存分配大小:62, 长度: 46, 引用记数: 1
内存地址: 13044040,内存分配大小:62, 长度: 47, 引用记数: 1
内存地址: 13044040,内存分配大小:66, 长度: 48, 引用记数: 1
内存地址: 13044040,内存分配大小:66, 长度: 49, 引用记数: 1
内存地址: 13044040,内存分配大小:66, 长度: 50, 引用记数: 1
我查到的字符串结构的说明跟你说的相同,如下:
串变量名:Str(隐含的指针)
│
↓
┌──┬──┬──┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬─┐
│spac│ref │len │││││(字符序列)│││││#0│
└──┴──┴──┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴─┘
spac:Cardinal,分配的串空间总长
ref :Integer,引用记数
len :Cardinal,实际串长,不大于串定义长度
可见一个AnsiString占用的空间总长(byte)为:[spac]=4+4+4+串定义长度+1。