Object Pascal中String类型的内幕探讨

原创 2001年12月11日 09:25:00
 

Object Pascal中,String(准确的说是AnsiString)是一种可变长度的字符串,通过PChar(AString)可以将其转换为与Windows API相兼容的字符指针类型。事实上,String类型就是一个指针,你可以用Sizeof去读取它的大小,不论字符串的实际长度是多少,Sizeof(AString)永远是4。String与一般的Null-Terminated字符指针不同的是,String还要保留另外的一部分空间,用于记录字符串长度和引用计数等信息。String类型在内存中的确切格式如下:

 

4字节)分配大小+(4字节)引用计数+(4字节)字串长度+(不定长)字符数组+(1字节)$0结束字符

 

为了验证这一点,我们可以在程序中添加一个作用域为private的String变量,在程序中动态改变它的长度和内容,同时观察它的分配大小和长度发生了什么变化。另外,为了观察引用计数的变化,只有在两个字符串互相复制的时候才能体现出来,我们在程序中也要实现这一点。

 

请新建一个Application,在窗体上放置一个Edit,一个ListBox和三个Button。其中,Edit用来改变字符串的内容;ListBox用来记录跟踪信息;三个按钮分别用于观察字符串的当前状况,观察字符串的引用计数变化情况和清空列表内容。

 

Form的声明中添加一个变量:

type

  TForm1=class(TForm)

   ...

   private

s : string;

end;

 

添加三个按钮的事件处理如下:

procedure TForm1.Button1Click(Sender: TObject);

var

  psz : PChar;

  pdw : PDWORD;

  dw1, dw2, dw3 : DWord;

begin

  s := Edit1.Text;

  psz := PChar(s);

  pdw := PDWORD(psz);

  Dec(pdw);Dec(pdw);Dec(pdw);

  dw1 := pdw^;

  Inc(pdw);dw2 := pdw^;

  Inc(pdw);dw3 := pdw^;

  ListBox1.Items.Add( Format('[Current]Size:%d, Ref:%d, Len:%d',

                          [dw1,dw2,dw3]) );

end;

 

procedure TForm1.Button2Click(Sender: TObject);

var

  psz : PChar;

  pdw : PDWORD;

  dw1, dw2, dw3 : DWord;

  s2 : string;

  p1, p2 : Pointer;

begin

  s := Edit1.Text;

  psz := PChar(s);

  pdw := PDWORD(psz);

  Dec(pdw);Dec(pdw);Dec(pdw);

  dw1 := pdw^;

  Inc(pdw);dw2 := pdw^;

  Inc(pdw);dw3 := pdw^;

  ListBox1.Items.Add( Format('[Before assign]Size:%d, Ref:%d, Len:%d',

                         [dw1,dw2,dw3]) );

  s2 := s;

  p1 := Pointer(PChar(s));

  p2 := Pointer(PChar(s2));

  ShowMessage(Format('p1=%p,p2=%p',[p1,p2]));

  psz := pChar(s);

  pdw := PDWORD(psz);

  Dec(pdw);Dec(pdw);Dec(pdw);

  dw1 := pdw^;

  Inc(pdw); dw2 := pdw^;

  Inc(pdw); dw3 := pdw^;

  ListBox1.Items.Add( Format('[After assign]Size:%d, Ref:%d, Len:%d',

                        [dw1,dw2,dw3]) );

  s2 := s2 + 'Another string';

  p1 := Pointer(PChar(s));

  p2 := Pointer(PChar(s2));

  ShowMessage(Format('p1=%p,p2=%p',[p1,p2]));

  psz := pChar(s);

  pdw := PDWORD(psz);

  Dec(pdw);Dec(pdw);Dec(pdw);

  dw1 := pdw^;

  Inc(pdw); dw2 := pdw^;

  Inc(pdw); dw3 := pdw^;

  ListBox1.Items.Add( Format('[After COW]Size:%d, Ref:%d, Len:%d',

                        [dw1,dw2,dw3]) );

end;

 

 

procedure TForm1.Button3Click(Sender: TObject);

begin

  ListBox1.Items.Clear;

end;

 

如果你对指针的概念比较清楚的话,上面的代码是不难理解的。下面是该程序的输出结果:

[Current]Size:22, Ref:5, Len:5

[Before Assign]Size:22, Ref:2, Len:5

[After Assign]Size:22, Ref:3, Len:5

[After COW]Size:22, Ref:2, Len:5

 

观察上述结果,可以得出几个结论:

1.“分配大小”和“字串长度”之间存在着一种固定的数量关系,即分配大小=字串长度+17。为什么会有这种关系?请你再看一看String类型的内存分布:(4字节)分配大小+(4字节)引用计数+(4字节)字串长度+(不定长)字符数组+(1字节)$0结束字符,4+4+4+(strlen)+1,应该是13+(strlen)才对,也就是说应该还有4字节的空间,其用途尚不清楚。值得一提的是,如果你将字符串清空,那么Len的结果可能不是你所想象的0,而是一个让你大吃一惊的数字。

2.因为分配大小和字串长度都是用4字节来表示的,而且String类型是动态分配内存,所以字符串最大可能的长度应该是2^32-17个字节。

3.在拷贝字符串的时候,Object Pascal并不是把字符串简单的复制一份,而是采取了引用计数的方法,将两个字符串指向同一个内存空间,同时引用计数加1。当字符串变量被清除的时候,引用计数减1,如果引用计数已经减为0,表明该字符串可以真正被清除了。显然,这种方法比复制整个字符串的效率要高。

4.在给字符串赋值的时候,Object Pascal首先会检查字符串的引用计数是否为1。如果是,按照一般的方法直接赋值即可;否则,就说明有两个以上字符串指向同一个地址,这种情况就复杂多了。Object Pascal使用的是Copy-on-Write机制(COW),为当前字符串另外开辟一个缓冲区,将新内容拷入;同时,原来的字符串引用计数要减一。

5.知道了String的内存布局,我们也就知道了PChar(str)的意义了。不过,使用PChar的同时也就丢失了String的动态增长和引用计数的功能,所以一定要小心,另外要注意PChar长度的计算和字符串长度一定要同步,否则会出问题。比如,下面的代码就不能正常工作:

var

  str : string;

begin

  SetLength(str,256);

  GetWindowDirectory(PChar(str),256);

  str := str + ‘/win.ini’;

end;

这样的结果是不正确的。之所以不正确,是因为SetLength将字符串长度设成了256,而PChar计算的长度只到第一个$0为止。正确的方法应该是:

SetLength(str,256);

GetWindowsDirectory(PChar(str),256);

SetLength(str,StrLen(PChar(str)));

str := setr + ‘/win.ini’;

 

 

说明:上面的程序是在Delphi 5下测试通过的。Borland并不保证String的内存结构在以后的Delphi版本中会保持不变,所以,上述例子只是作为测试用,实际的程序中不应该这样使用String,谨此说明。

[Object Pascal] String类型结构详解

在Object Pascal中,String(准确的说是AnsiString)是一种可变长度的字符串,通过PChar(AString)可以将其转换为与Windows API相兼容的字符指针类型。事实上...
  • space_ss
  • space_ss
  • 2011年08月24日 11:08
  • 673

Android中将List<Map<String, Object>>类型数据与字符串的相互转化

把List>类型的数据转换为字符串,存入数据库,从数据库取出字符串,转换为List>类型数据: 1)把List>转换为字符串 List> ls = new ArrayList>(); ...
  • a2500100455
  • a2500100455
  • 2015年02月11日 14:47
  • 8890

Record记录和变体记录

[delphi] view plaincopy //Integer类型刚好是4个字节,ShortInt类型是1个字节,但是Windows中内存是4字节分配,   //所...
  • yt_maomao
  • yt_maomao
  • 2014年07月03日 10:58
  • 1696

pascal 指针 讲解

指  针 指针的动态变量       1.定义指针类型     在Turbo Pascal中,指针变量中存放的某个存储单元的地址,即指针变量指向某个存储单元。一个指针变量仅能指向某一种类型...
  • zz_ylolita
  • zz_ylolita
  • 2014年04月12日 21:39
  • 1326

[Object Pascal] String类型结构详解

在Object Pascal中,String(准确的说是AnsiString)是一种可变长度的字符串,通过PChar(AString)可以将其转换为与Windows API相兼容的字符指针类型。事实上...
  • space_ss
  • space_ss
  • 2011年08月24日 11:08
  • 673

Java强制类型转换--object对象转换为String的一些总结

Java强制类型转换
  • DianaCody
  • DianaCody
  • 2014年04月21日 09:04
  • 3114

走进c#(类型转换的一个类Object对象转int String double byte[] Stream)

转自:http://www.cnblogs.com/dengw009/archive/2011/07/22/2114258.html using System; using System.Col...
  • dqvega
  • dqvega
  • 2012年04月25日 15:34
  • 8551

由String的内存分配了解Object和基础数据类型内存分配

先执行以下一小段代码:public static void main(String[] args) { String name = "LinDa"; String na...
  • wow4464
  • wow4464
  • 2016年03月16日 11:08
  • 417

**JS数据类型之 object 可以与 String 相等**

JS数据类型之 object 可以与 String 相等 今天在浏览器和微信开发者工具测试网页的时候,发现写出的react组件无法渲染出来,多番调试终于发现问题所在,废话不多说上代码。先来看一般浏览器...
  • cdp19930414
  • cdp19930414
  • 2016年10月13日 16:42
  • 147

Mongo的morphia读取Map<String, List<Object>>类型数据的问题

Mongo的morphia读取Map>的bug
  • zhglance
  • zhglance
  • 2017年06月14日 18:21
  • 669
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Object Pascal中String类型的内幕探讨
举报原因:
原因补充:

(最多只允许输入30个字)