Why System.SetLength(Str, Len) causes the address of Str changes?
Code Illustration
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;
Question
- Why
System.SetLength(Str, Len)
causes the address of Str changes? - Is there a way to nullify this side effect of
SetLength
so that I don't have to reassign the new address of Str &nbs开发者_运维技巧p;to PStr ?
As the help on System.SetLength says "Following a call to SetLength, S is guaranteed to reference a unique string or array—that is, a string or array with a reference count of one. If there is not enough memory available to reallocate the variable, SetLength raises an EOutOfMemory exception."
It always reallocates the string, that's why the address changes. It is also a way to get a string not used by anything else.
Update: to be fully correct, it's better to rephrase the statement above "it's safer to assume it always reallocates the sting". See comments below for an explanation.
I don't use Delphi but, clearly, the address changes when more memory must be allocated (e.g. the string needs to get bigger).
Since a string may not have any free space past the memory being used, it would be impossible to make a string longer without ever moving it.
You need to keep your reassignment.
No per Jonathan Wood, and additionally please note that not only length, every time you do anything with the string (even trim it) you have to be ready that it's address changes.
This is because when you copy a string:
var s1, s2: string;
begin
s1 := 'test'+IntToStr(SomeNumber);
s2 := s1;
Delphi actually copies just a reference, but increments reference counter. s1 and s2 now point to the same memory, but Delphi "remembers" that this memory is used from two places.
If you now try to trim s1:
SetLength(s1, 4);
Delphi figures s2 needs to stay unchanged, and so it does not trim s1 inplace, but instead copies four bytes to a new place. You now have s1 and s2 pointing to different memory adresses, each with reference counter of 1.
You usually cannot and should not try to predict when this will happen, just remember that the pointer to the string is valid only while the string stays unchanged.
The behavior is inherent to Delphi's String type.
If you want a consistent address use a PChar instead of String.
To answer your question...
1.Like ldsandon stated, SetLength guarantee the string will reference a unique string. Since you variable is assigned a constant string, the said string must have a reference count of at least 2 and thus can't realloc the memory in-place.
If your string was initialized by, for exemple, StringOfChar('A',5)
, the address of your string might not change after SetLength
(Though it wouldn't be guaranteed)
2.No, I don't think so. It's like asking for the following:
procedure someproc
var Obj1, obj2 : TObject;
begin
[...]
obj2 := Obj1;
Obj1 := nil;
//I want Obj2 to be also nil here since obj1 is nil...
end;
if you really want to be absolutely sure the address of PStr is the same as the string, there is 2 options I can think of right now, neither of which I can really recommend.
First:
procedure TForm1.FormCreate(Sender: TObject);
var
Str: string;
PStr: PChar absolute Str;
begin
But here, do NOT modify PStr as it will break the Str variable.
Second:
procedure TForm1.FormCreate(Sender: TObject);
var
Str: string;
function PStr: PChar
begin
Result := PChar(Str);
end
begin
But it's always better to just use "PChar(Str)" wherever you need to access your string as a Pchar.
Also, don't forger that if you write to the string through a PChar variable, you should ensure that the string has a reference count of 1 first(By calling UniqueString or SetLength).
精彩评论