Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1297176
  • 博文数量: 436
  • 博客积分: 7854
  • 博客等级: 少将
  • 技术积分: 3225
  • 用 户 组: 普通用户
  • 注册时间: 2007-12-18 16:30
文章分类

全部博文(436)

文章存档

2013年(2)

2012年(56)

2011年(70)

2010年(308)

分类:

2010-08-13 21:45:19

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。
阅读(3601) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~