Why does this one PAnsiChar get chopped when converted to AnsiString?

443 views Asked by At

Please consider the following program:

program SO41175184;

{$APPTYPE CONSOLE}

uses
  SysUtils;

function Int9999: PAnsiChar;
begin
  Result := PAnsiChar(AnsiString(IntToStr(9999)));
end;

function Int99999: PAnsiChar;
begin
  Result := PAnsiChar(AnsiString(IntToStr(99999)));
end;

function Int999999: PAnsiChar;
begin
  Result := PAnsiChar(AnsiString(IntToStr(999999)));
end;

function Str9999: PAnsiChar;
begin
  Result := PAnsiChar(AnsiString('9999'));
end;

function Str99999: PAnsiChar;
begin
  Result := PAnsiChar(AnsiString('99999'));
end;

function Str999999: PAnsiChar;
begin
  Result := PAnsiChar(AnsiString('999999'));
end;

begin
  WriteLn(Int9999); // '9999'
  WriteLn(Int99999); // '99999'
  WriteLn(Int999999); // '999999'

  WriteLn(string(AnsiString(Str9999))); // '9999'
  WriteLn(string(AnsiString(Str99999))); // '99999'
  WriteLn(string(AnsiString(Str999999))); // '999999'

  WriteLn(string(AnsiString(PAnsiChar(AnsiString(IntToStr(9999)))))); // '9999'
  WriteLn(string(AnsiString(PAnsiChar(AnsiString(IntToStr(99999)))))); // '99999'
  WriteLn(string(AnsiString(PAnsiChar(AnsiString(IntToStr(999999)))))); // '999999'

  WriteLn(string(AnsiString(Int9999))); // '9999'
  WriteLn(string(AnsiString(Int99999))); // '9999' <----- ?!
  WriteLn(string(AnsiString(Int999999))); // '999999'

  ReadLn;
end.

Only in one of these cases does the string lose a single character, in Delphi 2010 and Delphi XE3 both. With FPC the same program works correctly. Switching to PChar also makes the problem disappear.

I suppose it has something to do with memory management, but I don't have enough of a clue where to look to do a meaningful investigation. Could anyone clarify?

1

There are 1 answers

13
AudioBubble On BEST ANSWER

Dynamically created strings are reference counted and deallocated when no references remain.

Result := PAnsiChar(AnsiString(IntToStr(99999)));

causes a temporary AnsiString to be created, its address taken via the cast to PAnsiChar, and then the temporary string deallocated. The resulting pointer points to now-unclaimed memory that may be overwritten for pretty much any reason, including during the allocations of more strings.

Neither Delphi nor FPC clears memory by default during deallocations, so if the memory hasn't been re-used yet, you may get lucky when reading what used to be there. Or, as you saw, you may not.

When returning PAnsiChar like this, you need an agreement between caller and callee on memory management. You need to make sure you do not free the memory early, and you need to make sure your callers know how to free the memory afterwards.

Remy Lebeau points out that this deallocation happens when the procedure or function returns. If there is another statement after the assignment to Result, the string will still be available. This is normally correct, but there are also cases where it the temporary string gets deallocated before the return, for example when you create temporary strings in a loop. I would not recommend using temporary objects after the statement that creates them concludes, even in cases where it is valid, because it makes it too hard to verify whether the code is correct. For those cases, just use an explicit variable.