为什么局部字符串变量的引用计数是 -1???(100分)

  • 主题发起人 主题发起人 hbwin2001
  • 开始时间 开始时间
H

hbwin2001

Unregistered / Unconfirmed
GUEST, unregistred user!
procedure TForm1.Button1Click(Sender: TObject);
var TempString:string;
begin
TempString:='abc';
showmessage(inttostr(Pinteger(Integer(TempString)-8)^));
end;
显示的结果是-1

但如果把字符串声明为全局变量或者类成员,引用计数就正确了,为什么?请高手赐教
 
樊东东2004年9月10日
说明:本人不擅长文笔,只是自己的体会写下,有错误之处请指正,谢谢!

一、 String与ShortString的区别
ShortString是Delphi的传统的Pascal类型的字符串(区别与C类型的字符串),最多能存储255个字节。而String是Delphi中特有的字符串类型,并且自动管理内存分配和释放,可以说是一种智能的字符串,并且他能存储字符串的长度可以说是无限制的(内存的限制)。
二、 了解ShortString
学过Pascal的都知道,ShortString类型的字符串的第一个字节是字符串字符的个数,从第二个开始才是字符串的字符,并且ShortString是定长的,是256个字节,即sizeof(ShortString)是256,第一个字节是存储长度,所以只能存储255个字节。
三、 了解String
那String到底是什么样的结构呢?那sizeof(String)是多少呢?如果没有错的话,应该是4。也许大家会感到很惊讶,为什么始终为4呢?经过分析,原来String有两部分构成,第一部分即是4个字节长度的指针(就是sizeof(String)=4的那个),第二部分就是实际的字符串了,而第一部分的指针指向的就是第二部分的字符串。

举例:
var
String str

begin
str := ‘DELPHI’

end


取str的地址,即@str,我们会得到具体的地址$12F418(不同的环境可能不一样)。
然后我们查看地址为$12F418处的内存,我们就会发现’D’,’E’,’L’,’P’,’H’,’I’,’#0’字符。

这就是string的基本结构,可是string也并不是那么简单的,其实在在地址为$12F410开始就是string的第二部分,只是我们平时用不着关心而已,都是由编译器处理。从$12F410到$12F417的8个字节,其中前面四个字节记录字符串被引用的次数,如果是$FFFFFFFF则表示此字符串是常量。后面四个字节记录字符串的长度,不包括结束符#0。
四、 分析String
接下来我们用源码分析String。
1、 字符串的赋值
当我们给字符串赋值时,将会调用
procedure _LStrLAsg(var dest
const source)

asm
{ -> EAX pointer to dest }
{ EDX source }

TEST EDX,EDX
JE @@sourceDone { if source = nil then exit }

{ bump up the ref count of the source }

MOV ECX,[EDX-skew].StrRec.refCnt { 获取引用次数}
INC ECX
JLE @@sourceDone { literal assignment -> jump taken }{如果字符串是常量,则不进行引用计数}
LOCK INC [EDX-skew].StrRec.refCnt
@@sourceDone:

{ we need to release whatever the dest is pointing to }

XCHG EDX,[EAX] { fetch str }{给目的字符串赋值}
TEST EDX,EDX { if nil, nothing to do}{判断目的字符串是否首次赋值}
JE @@done { 如果是,则退出,如果不是,则根据目的字符串的原字符串的引用计数进行适当操作}
MOV ECX,[EDX-skew].StrRec.refCnt {fetch fCnt }
DEC ECX { if < 0: literal str}
JL @@done {如果目的字符串的原字符串是常量,则直接退出}
LOCK DEC [EDX-skew].StrRec.refCnt { threadsafe dec refCount}{目的字符串的原字符串不是常量, 目的字符串的原字符串引用计数减一}
JNE @@done
LEA EAX,[EDX-skew].StrRec.refCnt { if refCnt now zero, deallocate}
CALL _FreeMem{如果目的字符串的原字符串引用计数减一后等于零,则释放目的字符串的原字符串占用的内存}
@@done:
end


2、 取得字符串长度
function _LStrLen(const s: AnsiString): Longint

asm
{ -> EAX str }

TEST EAX,EAX
JE @@done {如果s是空字符串,则直接退出,返回值为0 }
MOV EAX,[EAX-skew].StrRec.length
{如果s的长度}
@@done:
end


3、 设置字符串长度
procedure _LStrSetLength{ var str: AnsiString
newLength: Integer}

asm
{ -> EAX Pointer to str }
{ EDX new length }

PUSH EBX
PUSH ESI
PUSH EDI
MOV EBX,EAX
MOV ESI,EDX
XOR EDI,EDI

TEST EDX,EDX
JLE @@setString

MOV EAX,[EBX]
TEST EAX,EAX
JE @@copyString

CMP [EAX-skew].StrRec.refCnt,1
JNE @@copyString

SUB EAX,rOff
ADD EDX,rOff+1
PUSH EAX
MOV EAX,ESP
CALL _ReallocMem
POP EAX
ADD EAX,rOff
MOV [EBX],EAX
MOV [EAX-skew].StrRec.length,ESI
MOV BYTE PTR [EAX+ESI],0
JMP @@exit

@@copyString:
MOV EAX,EDX
CALL _NewAnsiString
MOV EDI,EAX

MOV EAX,[EBX]
TEST EAX,EAX
JE @@setString

MOV EDX,EDI
MOV ECX,[EAX-skew].StrRec.length
CMP ECX,ESI
JL @@moveString
MOV ECX,ESI

@@moveString:
CALL Move

@@setString:
MOV EAX,EBX
CALL _LStrClr
MOV [EBX],EDI

@@exit:
POP EDI
POP ESI
POP EBX
end;
 
procedure TForm1.Button1Click(Sender: TObject);
var
TempString: AnsiString;
i: Integer;
begin
for i := 1 to 300 do
TempString := TempString + 'a';
showmessage(inttostr(Pinteger(Integer(TempString)-8)^));
end;

这样引用数就为1了,可能字符少时,编译器优化把ansistring的内存从栈中分配且不计数引用,否则从堆中分配,并计数引用。
 
帮顶!

╭=========================================╮

80G海量源代码,控件,书籍全免费狂下不停!

http://www.source520.com

╰=========================================╯
 
后退
顶部