关于String和Pointer,Addr()到底都作了些什么?(100分)

  • 主题发起人 主题发起人 leoleoleo
  • 开始时间 开始时间
L

leoleoleo

Unregistered / Unconfirmed
GUEST, unregistred user!
如果我没记错的话String应该是一个指针,指向字符串实际存储的地址(内容和长度)
可是写代码验证时,奇怪的事情发生了
------------------代码如下-----------------------
procedure TForm1.Button1Click(Sender: TObject);
var
szStr_1:string;
pChr:PChar;
pInt:^Integer;
begin
Memo.Clear;
szStr_1:='abc';
pInt := addr(szStr_1);

Memo.Lines.Add(Format('Addr(szStr_1)
Address:%x
Content:%x', [Integer(pInt), pInt^]));
pChr := Addr(szStr_1[1]);
Memo.Lines.Add(Format('Addr(szStr_1)
Address:%x
Content:%x', [Integer(pInt), pInt^]));

Memo.Lines.Add(Format('Addr(String[1]
Address:%x
Content:%s', [Integer(pChr), pChr^]));
end;
---------------------------运行结果如下------------------------
Addr(szStr_1)
Address:12F59C
Content:*46C1AC*
Addr(szStr_1)
Address:12F59C
Content:*D62174*
Addr(String[1]
Address:D62174
Content:a

*中的内容应该指向字符串存放地址,可是两次结果竟然不一样
在这期间只有一句 pChr := Addr(szStr_1[1]);
难道Addr(szStr_1[1]),会影响szStr存放的指针值么?
百思不得其解,盼高手回答
 
挺有意思。不过这个问题的真正答案想必要borland 的人才能回答。
我也试了试,现在说说我的看法。
在调用了pChr := Addr(szStr_1[1]);后,szStr_1中的值确实变了。
我觉的在给szStr_1负值后,其中的值并未指向szStr_1[1]的地址,而是指向了一个12个字节的结构,那个结构中保存了字符串的真正地址。至于那12个字节中到底存了什么,我想那是Delphi处理字符串机制的一部分,我猜不到。以下是我的例子:
procedure TForm1.Button1Click(Sender: TObject);
var
str,str1:String;
pc:PChar;
pi:^Integer;
begin
str:='abc';
pi:=addr(str);
Memo.Lines.Add(IntToStr(Integer(addr(str))));
Memo.Lines.Add(IntTostr(pi^));

pc:=@str[1];
Memo.Lines.Add(IntToStr(Integer(@str)));
Memo.Lines.Add(IntTostr(pi^));
memo.Lines.Add('');

str1:=str;
str:='fghg';
Memo.Lines.Add(IntToStr(Integer(addr(str))));
Memo.Lines.Add(IntTostr(pi^));
pc:=addr(str[1]);
Memo.Lines.Add(IntToStr(Integer(@str)));
Memo.Lines.Add(IntTostr(pi^));
memo.Lines.Add('');

Memo.Lines.Add(IntToStr(Integer(@str1[1])));
Memo.Lines.Add(IntToStr(Integer(@str1[2])));
Memo.Lines.Add(IntToStr(Integer(@str1[3])));

Memo.Lines.Add(IntToStr(Integer(@str[1])));
Memo.Lines.Add(IntToStr(Integer(@str[2])));
Memo.Lines.Add(IntToStr(Integer(@str[3])));
end;

============================================
1242664
4521512
1242664
13974376

1242664
4521524
1242664
13969028

13974376
13974377
13974378
13969028
13969029
13969030
 
用匯編就可以得到直接的答案了。
 
k8nt2k:
12个字节是不是根据两次内存地址(4521524-4521512)的偏移得出来的?
如果你把str:='abc'改为'abcd'或更长,就是16个字节,似乎证明不了什么。
用代码试一下会发现szStr_1第一次指向的地址,也是字符串'abc'。
但是 经过pChr := Addr(szStr_1[1])后,Delphi似乎作了一次内存复制,前后两次指向地址字符串内容相同。

Delphi 5 开发人员指南中倒是有过相关说明:
[purple]警告Borland没有将长字符串类型的内部结构写到文档中,并保留了在Delphi后续版本中修改
长字符串内部格式的权利。在这里介绍的情况主要是帮助你理解A n s i S t r i n g类型是怎样工作的
并且在程序中要避免直接依赖于AnisString结构的代码。
程序员如果在Delphi 1.0中避免了使用字符串的内部结构,则把代码移植到Delphi 2.0没有
任何问题。而依赖于字符串内部结构写代码的程序在移植到Delphi 2.0时必须修改。[/purple]

不知道我遇到的情况是不是属于直接依赖于AnisString结构的代码。不过这样也太@#$#%$%了
 
其实string定义的类型是动态类型,由delphi来维持,比较复杂,对于addr(str)应该没什么意义.因为你不能通过地址来得到你想要的数据,因为任何操作都可改变其地址.
比如 pInt := addr(szStr_1);
szStr_1:='ddd';
szStr_1:='adfa';
你会发现s^两次的内容会不同,可以去测试
问题中pChr := Addr(szStr_1[1]);也改变了其地址,不过这个地址和pchr地址相同,我们可以设想,由于string动态改变,每次操作string类型数据后,如果取其地址,再用^ 取其内容,它都会以最后一个操作完的地址来回答你.
 
高深莫测!!
 
搞明白了,Delphi在对String赋值的时候,并没有做内存拷贝,而是将String变量中的地址指向值地址,便于加快访问速度。
但如果赋值是常量,则Delphi判断代码有任何意图改写常量的可能时,会重新分配内存并做串拷贝。示例代码中的pInt := addr(szStr_1)因为直接访问常量地址,就被Delphi视作有修改意图。如果此行换成szStr_1 := szStr_1 + 'def' 则会出现同样的问题。
如果初始化不指向常量地址,szStr_1:='abc';换成szStr_1 := Edit1.Text,也不会有内存重新分配的现象。
 
散分,如有不同见解欢迎继续讨论
 
多人接受答案了。
 
你的解释不能解决为什么最后pint和pchr地址内容相同的问题.
pChr := Addr(szStr_1[1]);此句产生一个pchar字符,即pchr:='abc'#0这样一个带null的字符,
照理pint是szStr_1的地址(不管它变不变)那为什么两个地址会相等呢.
我个人觉得string和pchar转换中delphi有套潜在规则
 
tutu6688:
不是pint=pchr,是pint^=pchr。pint指向string变量地址addr(szStr_1),这个地址的内容还是一个指针,并指向真正字符串存储的地址addr(szStr_1[1]),也就是说(pint^)^才是字符串的内容。在任何情况下pint是不可能等于pchr的。
 
to leolenleo:
我写错了,是pint^:=pchr.开始我不明白为什么会相等.
现在我明白了,pchar直接取地址,并添加'null'符号,对于string类型时,pchar就干脆直接取其地址,当pchar重新赋值时,地址即发生改变,指向再也不是原来的Pint^.
而对于string类型,是个引用变量,包含长度,被引用记数,和字符地址,而且是个自我管理生存期的.当记数为o时,就自我销除.可以用setlength来设置起长度,也即其连续地址空间个数.只有字符地址是可操作的,其他地址被delphi隐蔽了.所以addr(str)能得到其首地址值,
我用chr((pbyte(pInt^))^)得到字符a,证明没错.
回想整个问题,下面这句
pChr := Addr(szStr_1[1]);
最让我搞不懂.它使pInt改变了,按你的讲法也行不通.它并非对常量改变.
如: var s,s1:string;
pInt:PInteger;
s1:='aaa';
s:=s1;
pInt:=addr(s);
memo.items.add(format('%x',[pInt^]));
s:=s+edit1.text;
memo.items.add(format('%x',[pInt^]});

你会发现两个内容并不一样.
这说明string有它自己的一套管理内寸地址的方法.
 
tutu6688:
不一样就对了,这正是我最后的结论,问题的关键就在s指向了常量'aaa',不信你把s1:='aaa'换成s1:=Edit1.text试一下,两个内容肯定一样。
有几个概念要先明确
1,addr(str)能得到其首地址值
//确切地说addr(str)是指向首地址的地址,Addr(szStr_1[1])才是串的首地址

2,pChr := Addr(szStr_1[1])并非对常量改变.
//是有意图改变,因为你已经获得串的首地址,已经有绕过string机制,利用指针修改 常量内存值得可能。

var
s,s1:string;
pInt:PInteger;
begin
s1:='aaa'
//s1^ 指向常量'aaa'的首地址地址
s:=s1
//s^ 同样指向常量'aaa'的首地址,'aaa'的首地址 - 8 中的引用计数+1
pInt:=addr(s)
//pint^==s^
memo.items.add(format('%x',[pInt^]))

s:=s+edit1.text
//系统判定意图修改'aaa'常量,重新分配内存并拷贝串,s1^(pInt^)指向新地址。
memo.items.add(format('%x',[pInt^]})

end;
 
to leoleoleo:
你的第一条没错,第二条还有疑问.
什么叫试图改变.不敢苟同.
假如你去掉
pChr := Addr(szStr_1[1])这句
单纯这样按下面取地址,也会发现地址变了.
Memo.Lines.Add(Format('Address:%x;', [Integer(addr(szStr_1[1]))]));
这应该没试图改变吧,
 
或许Delphi认为有意图呢,难道这就是所谓的内部机制?
 

Similar threads

S
回复
0
查看
1K
SUNSTONE的Delphi笔记
S
S
回复
0
查看
900
SUNSTONE的Delphi笔记
S
S
回复
0
查看
3K
SUNSTONE的Delphi笔记
S
S
回复
0
查看
2K
SUNSTONE的Delphi笔记
S
I
回复
0
查看
1K
import
I
后退
顶部