为什么System.SetLength(Str,Len)会导致Str的地址发生变化?

时间:2010-12-22 15:18:11

标签: delphi

代码插图

procedure TForm1.FormCreate(Sender: TObject);
var
  Str: string;
  PStr: PChar;
begin
  Str := 'This a string.';
  PStr := Pointer(Str); // PStr holds the address of the first char of Str
  ShowMessage(IntToStr(Longint(PStr))); // It displays e.g. 4928304

  Setlength(Str, 20);

  // I don't know what actually happens in the call for SetLength() above,
  // because the address of Str changes now, so the PStr not valid anymore.

  // This is a proof of the fact
  PStr := Pointer(Str);
  ShowMessage(IntToStr(Longint(PStr))); // It's now different, e.g. 11423804
end;

问题

  1. 为什么System.SetLength(Str, Len)会导致 Str 的地址发生变化?
  2. 有没有办法可以消除SetLength的副作用,这样我就不必将 Str 的新地址重新分配给 PStr ?< / LI>

5 个答案:

答案 0 :(得分:10)

正如help on System.SetLength所说“在调用SetLength之后,S 保证引用唯一字符串或数组 - 即带引用计数的字符串或数组如果没有足够的可用内存来重新分配变量,SetLength会引发EOutOfMemory异常。

它总是重新分配字符串,这就是地址变化的原因。它也是一种获取其他任何东西都不使用的字符串的方法。

更新:为了完全正确,最好重新改写上面的陈述“假设它总是重新分配刺痛更安全”。请参阅以下评论以获得解释。

答案 1 :(得分:8)

我不使用Delphi,但显然,当必须分配更多内存时(例如字符串需要变大),地址会发生变化。

由于字符串在使用的内存之后可能没有任何空闲空间,因此如果没有移动它就不可能使字符串更长。

您需要保留重新分配。

答案 2 :(得分:4)

不是每个乔纳森伍德,另外请注意,不仅长度,每次你用字符串任何(甚至修剪它)你必须准备好它的地址变化。

这是因为当你复制一个字符串时:

var s1, s2: string;
begin
  s1 := 'test'+IntToStr(SomeNumber);
  s2 := s1;

Delphi实际上只复制了一个引用,但增加了引用计数器。 s1和s2现在指向相同的内存,但是Delphi“记得”这个内存是从两个地方使用的。

如果您现在尝试修剪s1:

SetLength(s1, 4);

Delphi数据s2需要保持不变,因此它不会修剪s1到位,而是将四个字节复制到一个新位置。你现在有s1和s2指向不同的内存地址,每个地址的引用计数器为1。

你通常不能也不应该试着预测何时会发生这种情况,只要记住指向字符串的指针只有在字符串保持不变时才有效。

答案 3 :(得分:1)

这种行为是Delphi的String类型所固有的。

如果您想要一致的地址,请使用PChar而不是String。

答案 4 :(得分:1)

回答你的问题...

1.如ldsandon所述,SetLength保证字符串将引用唯一的字符串。由于您为变量分配了一个常量字符串,因此该字符串必须具有至少为2的引用计数,因此无法就地重新分配内存。 如果您的字符串已初始化,例如StringOfChar('A',5),则SetLength后字符串的地址可能不会更改(虽然无法保证)

2.不,我不这么认为。这就像要求以下内容:

  procedure someproc
  var Obj1, obj2 : TObject;
  begin
  [...]
    obj2 := Obj1;
    Obj1 := nil;
    //I want Obj2 to be also nil here since obj1 is nil...
  end;

如果你真的想要绝对确定PStr的地址和字符串相同,那么我现在可以想到两个选项,我都不能推荐它们。

首先:

procedure TForm1.FormCreate(Sender: TObject);
var
  Str: string;
  PStr: PChar absolute Str;
begin

但是在这里,不要修改PStr,因为它会破坏Str变量。

第二

procedure TForm1.FormCreate(Sender: TObject);
var
  Str: string;
  function PStr: PChar
  begin
    Result := PChar(Str);
  end
begin

但是,只要在需要将字符串作为Pchar访问的地方使用“PChar(Str)”总是更好。

另外,不要伪造如果你通过PChar变量写入字符串,你应该确保字符串的引用计数为1(通过调用UniqueString或SetLength)。