一个关于string分配内存的问题 (100分)

  • 主题发起人 snowrain
  • 开始时间
S

snowrain

Unregistered / Unconfirmed
GUEST, unregistred user!
PTest=^RTest;
RTest=Record
Name:string[20];
Identifier:integer;
Content:string;
end;

prcedure test;
var
p:pTest;
begin
New(p);
p^.Name:='...';
...
end;
这种情况下New(p)可以分配多少内存,结构中有一个Content是一个string变量,在分配内存时,具体应该分配给content多少内存?string这种变量我一直不是很清楚,它不像string[20]那么单纯,只分配21个字节。
也就是说,
string这类变量一旦放在record(结构)中,那么内存分配就不是很清楚了,就像一个指向这个record的指针,new一下具体应该怎么分配内存?
请大侠给我详细讲讲,谢谢。
 
string[20]是短字符串,相当于array[1..20] of char; 可以自动分配。
而直接声明为string则只是一个指针,Delphi的字符串操作中会自动管理这个指针,以及
分配空间,所以没有必要去关心具体分配了多少。
new(p)的时候相当于分配好了Content的字符串指针(nil)
 
放在record中跟在外面没什么区别,都是占用同样的字节数,一个string好象是255个字节长。
 
直接用string定义的是动态字符串,它跟根据所赋的值改变它的长度!
var
s: string;
begin
s:='ljl';
showmessage(inttostr(Length(s)));
s:='jlajaj;dla';
showmessage(inttostr(Length(s)));
end;
第一个show出来的是3,第二个是10,由此可证明“直接声明为string则只是一个指针”这
种说法是错的,指针的长度就是定长的!
 
to 伊天仇
不懂不要乱说,length是取字符串的长度,不是给变量分配的空间。
这里的length相当于strlen(pchar(s)),当然实际上Delphi做了更多的工作
string类型相当于
type String = record
Len: cardinal;
P: pchar;
end;
 
不,string相当于
type String = record
Pdata: Pointer;
end;
4个字节
 
new()能为一个指针分配指定长度的内存空间。
这时候p^.CONTEXT:=‘AAAA’(一定要有具体的东东);

在对PCHAR和POINTER类型分配内存时,因为他们
有长度可变特性,编译器就不知道要分配多少内存,
这时候就要用:GETMEM()和ALLOCMEM()。
 
to Pipi,我只是举个例子,实际上Pdata[0]还不是存放长度?
 
对于string[10]等方式的传统Pascal字符串定义,则[0]中存放字符串长度。
而对于string定义,我记得好象是一个以#0结尾的字符串,与C的字符串有
相似之处。
 
to Adnil
不对,你原来的结构是8个字节。
而且不是pdata[0]存放长度,pdata就开始存放字符串了
AnsiString, pdata-4存放长度,-8存放引用数
 
to Pipi:谢谢指正!
 
to Pipi.
“而且不是pdata[0]存放长度,pdata就开始存放字符串了
AnsiString, pdata-4存放长度,-8存放引用数”
是什么意思?有点胡涂了,到底什么地方(从哪个字节起)放长度,什么
地方放引用计数,又是从什么地方开始存放具体数据,能否举个例子解释一下,万分感谢!
 
to Adnil
“不懂不要乱说”:
这是个自由发言的地方,就算我说错了,你也没权不让我说!你敢说你没犯过错!
“length是取字符串的长度”:
读过书的都知道Length是长度的意思(虽然我E文一般)!

“这里的length相当于strlen(pchar(s)),当然实际上Delphi做了更多的工作
string类型相当于
type String = record
Len: cardinal;
P: pchar;
end;”
请问你这个理论是从哪来的,第一次听说String相当于一个Record类型,这倒愿洗耳恭听!
你不会小气到不鸟我吧!
我觉得我们好象扯远了,snowrain说的“这种情况下New(p)可以分配多少内存,结构中有
一个Content是一个string变量,在分配内存时,具体应该分配给content多少内存?”
我用它试了一下:
type
PTest=^RTest;
RTest=Record
Name:string[20];
Identifier:integer;
Content:string;
end;
var
R: RTest;
p:pTest;
begin
showmessage(inttostr(sizeof(R)));
new(p);
showmessage(inttostr(sizeof(p)));
dispose(p);
end;
R=32;
P=4;
这又该如何解释!
 
to 伊天仇:
Adnil是对的。你用filestream写string,肯定要出错
 
R=32;
P=4;
这又该如何解释!

应该是这样解释:
R是指向一个记录结构的内容,这个结构的大小是32,
Name:string[20]; 20
Identifier:integer; 4
Content:string; 8
P是一个指针,只占用4个字节。
 
为记录里string型分配内存的时候只分配一个指针,是四位长。如果只在程序里操作就没问题了,
delphi会自动为他分配内存及管理长度的,可是如果要保存到文件中,最好不要这样声明,而应
直接指定长度。
 
To 伊天仇:

无论什么类型的指针,只要是指针,Sizeof返回都是4(指针其实是赋与特殊功能的整数,所以大小和整数一样),
你可以试试Sizeof(TForm),或者Sizeof(PChar);

对于字符串,其实它是一个特殊的动态字符数组(动态数组和指针又有不可公割的关系),其实字符串长度是多少,取Sizeof都反回4;

如果字符串是动态数组,有人会问,为什么其他动态数组都是以0为下标开始的,而字符串确以1以下标开始,这个问题可是宝兰为提高字符串的性能想出来的;
我们知道PChar类型是以#0作为字符串结束的标志,所以当访问一个PChar类型的变量的时候,就需要遍历字符串的每一个字节,直到遇到#0为止;
如果可以先知道字符串的长度,再直接取相应长度的字符串不是更快吗,所以宝兰的工程师想到了在动态数组的第一个元素保存字符串的长度;
这是真的,然后把数组上标往后移一个;

请看下面的代码:

procedure TForm1.Button1Click(Sender: TObject);
Var s:String;
P:^Char;
C:Char;
begin
s:='adakdsdsd';
P:=@(s[1]);
Dec(p);//把指针往前移一位.
ShowMessage(IntToStr(ORD(P^)));
//显示指针指向的内容.
// ShowMessage(IntToStr(Sizeof(s)));
end;

各位试验的时候发现显示的是0,为什么,原来这个动态数组还不是一般的动态数组,
它的第一个元素和后面的元素的长度不一样的,因为第一个元素是字符串长度,是整数,所以是4位;
好了把程序改一改:

procedure TForm1.Button1Click(Sender: TObject);
Var s:String;
P:^Char;
C:Char;
begin
s:='adakdsdsd';
P:=@(s[1]);
Dec(p,4);//把指针往前移四位.也就是说整数的长度.
ShowMessage(IntToStr(ORD(P^)));
//显示指针指向的内容.
// ShowMessage(IntToStr(Sizeof(s)));
end;

对了吗,改动字符串再试试?
 
其他的不用说了,只Content是一个string变量,在分配内存时分配4字节,实际是一个字符串型的指针,是一
个指向在堆栈中的字符串结构的指针.
其说明请见<<Delphi5 开发人员指南>>的 "2.6.3 字符串"部分!
或查看http://www.delphibbs.com/delphibbs/dispq.asp?LID=1118494
复制如下:
2.6.3 字符串
字符串是代表一组字符的变量类型,每一种语言都有自己的字符串类型的存储和使用方法。
P a s c a l类型有下列几种不同的字符串类型来满足程序的要求:
• AnsiString 这是P a s c a l缺省的字符串类型,它由AnsiChar 字符组成,其长度没有限制,同时与
n u l l结束的字符串相兼容。
• ShortString 保留该类型是为了向后兼容Delphi 1.0,它的长度限制在2 5 5个字符内。
• WideString 功能上类似于A n s i S t r i n g,但它是由Wi d e C h a r字符组成的。
• PChar 指向n u l l结束的C h a r字符串的指针,类似于C的char * 或l p s t r类型。
• PAnsiChar 指向n u l l结束的A n s i C h a r字符串的指针。
• PWideChar 指向n u l l结束的Wi d e C h a r字符串的指针。
缺省情况下,如果用如下的代码来定义字符串,编译器认为是AnsiString 字符串:
v a r
S:string; //编译器认为S的类型是A n s i S t r i n g
当然,能用编译开关$ H来将s t r i n g类型定义为S h o r t S t r i n g,当$ H编译开关的值为负时, s t r i n g变量
是S h o r t S t r i n g类型;当$ H编译开关的值为正时(缺省情况),字符串变量是A n s i S t r i n g类型。下面的代码
演示了这种情况:
v a r
{ $ H - }
S1:string; //S1是S h o r t S t r i n g类型
{ $ H + }
S2:string; //S2是A n s i S t r i n g类型
使用$ H规则的一个例外是,如果在定义时特地指定了长度(最大在2 5 5个字符内),那么总是
S h o r t S t r i n g:
v a r
S: string[63]; //63个字符的S h o r t S t r i n g字符串
1. AnsiString类型
A n s i S t r i n g (或长字符串)类型是在Delphi 2.0开始引入的,因为Delphi 1.0的用户特别需要一个容易
使用而且没有2 5 5个字符限制的字符串类型,而A n s i S t r i n g正好能满足这些要求。
虽然A n s i S t r i n g在外表上跟以前的字符串类型几乎相同,但它是动态分配的并有自动回收功能,正
是因为这个功能A n s i S t r i n g有时被称为生存期自管理类型。Object Pascal能根据需要为字符串分配空间,
所以不用像在C / C + +中所担心的为中间结果分配缓冲区。另外, A n s i S t r i n g字符串总是以n u l l字符结束
的,这使得A n s i S t r i n g字符串能与Win32 API 中的字符串兼容。实际上,A n s i S t r i n g类型是一个指向在堆
栈中的字符串结构的指针。
警告Borland没有将长字符串类型的内部结构写到文档中,并保留了在Delphi后续版本中修改
长字符串内部格式的权利。在这里介绍的情况主要是帮助你理解A n s i S t r i n g类型是怎样工作的
并且在程序中要避免直接依赖于AnisString结构的代码。
程序员如果在Delphi 1.0中避免了使用字符串的内部结构,则把代码移植到Delphi 2.0没有
任何问题。而依赖于字符串内部结构写代码的程序在移植到Delphi 2.0时必须修改。
正如图2 - 1演示的,A n s i S t r i n g字符串类型有引用计数的功能,这表示几个字符串都能指向相同的
物理地址。因此,复制字符串因为仅仅是复制了指针而不是复制实际的字符串而变得非常快。
图2-1 显示了AnsiString在内存中分配的情况
当两个或更多的A n s i S t r i n g类型共享一个指向相同物理地址的引用时, D e l p h i内存管理使用了
c o p y - o n - w r i t e技术,一个字符串要等到修改结束,才释放一个引用并分配一个物理字符串。下面的例
子显示了这些概念:
v a r
S 1 , S 2 : s t r i n g ;
b e g i n
/ /给S 1赋值,S 1的引用计数为1
S1:='And now for something...';
S2:=S1; //现在S 2与S 1指向同一个字符串, S 1的引用计数为2
/ / S 2现在改变了,所以它被复制到自己的物理空间,并且S 1的引用计数减1
S2:=S2+'completely diff e r e n t 1 ' ;
e n d ;
生存期自管理类型
除了A n s i S t r i n g以外, D e l p h i还提供了其他几种生存期自管理类型,这些类型包括:
Wi d e S t r i n g、Va r i a n t、O l e Va r i a n t、i n t e r f a c e、d i s p i n t e r f a c e和动态数组,这些类型在本章稍后有
介绍,现在,我们重点讨论究竟什么是生存期自管理,以及它们是如何工作的。
生存期自管理类型,又被称为自动回收类型,是指那些在被使用时就占用一定的特殊资
源,而在它离开作用域时自动释放资源的类型。当然不同的类型使用不同的资源,例如,
A n s i S t r i n g类型在被使用时就为字符串占用内存,当它超出作用域时就释放被字符串占用的内
存。
 
to Pipi.
“而且不是pdata[0]存放长度,pdata就开始存放字符串了
AnsiString, pdata-4存放长度,-8存放引用数”
是什么意思?有点胡涂了,到底什么地方(从哪个字节起)放长度,什么
地方放引用计数,又是从什么地方开始存放具体数据,能否举个例子解释一下,万分感谢!
 
其实可以理解:
String这个数组的第0个元素包括两个整数,一个是记录数,另一个是长度,
其中记数是放在前面的,长度放在后面,而这两个值的Size都是4,所以把
String类型的变量s的第一个元素s[1]的指针往前移4位,就可以取到字符串长度,
再移4位可以取到记数:
请看下面的代码:

procedure TForm1.Button1Click(Sender: TObject);
Var s:String;
P:^Char;
C:Char;
begin
s:='adakdsdsd';
P:=@(s[1]);
Dec(p,4);//把指针往前移四位.也就是说整数的长度.
ShowMessage(IntToStr(ORD(P^)));//这里显示的是字符串的长度。
Dec(p,4);//再把指针往前移四位.也就是说整数的长度.
ShowMessage(IntToStr(ORD(P^)));//这里显示的是记数。
//显示指针指向的内容.
// ShowMessage(IntToStr(Sizeof(s)));
end;
 
顶部