为什么这个PAnsiChar在转换为AnsiString时会被切断?

Thi*_*ien 3 delphi delphi-2010 delphi-xe3

请考虑以下计划:

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.
Run Code Online (Sandbox Code Playgroud)

只有在其中一种情况下,字符串会丢失一个字符,在Delphi 2010和Delphi XE3中都是如此.使用FPC,相同的程序可以正常工作.切换到PChar也会使问题消失.

我想它与内存管理有关,但我没有足够的线索在哪里寻找有意义的调查.任何人都可以澄清吗?

小智 8

当没有引用时,动态创建的字符串将被引用计数和释放.

Result := PAnsiChar(AnsiString(IntToStr(99999)));
Run Code Online (Sandbox Code Playgroud)

导致临时AnsiString创建,其地址通过强制转换为PAnsiChar,然后临时字符串解除分配.生成的指针指向现在无人认领的内存,可能会因为任何原因而被覆盖,包括在分配更多字符串期间.

在解除分配期间,默认情况下Delphi和FPC都不会清除内存,所以如果内存还没有被重用,那么在阅读那里的内容时你可能会很幸运.或者,如你所见,你可能没有.

PAnsiChar像这样返回时,您需要在调用者和被调用者之间就内存管理达成协议.您需要确保不要尽早释放内存,并且需要确保您的呼叫者知道如何释放内存.

Remy Lebeau指出,当程序或函数返回时会发生这种释放.如果在赋值之后还有另一个语句Result,则该字符串仍然可用.这通常是正确的,但也有一些情况,临时字符串在返回之前被释放,例如在循环中创建临时字符串时.我不建议在创建它们的语句结束后使用临时对象,即使在它有效的情况下也是如此,因为它太难以验证代码是否正确.对于这些情况,只需使用显式变量.

  • @ThijsvanDien分配器很难预测,但相当确定.如果它在程序的一次执行中表现出某种特定的方式,那么很有可能它在所有执行中都会以这种方式运行.另外一点是'9999'的长度是4的平凡倍数.内存块通常与4字节边界对齐,这就解释了为什么部分覆盖恰好覆盖第五个字符. (2认同)
  • @ThijsvanDien - 尝试将随机行为转变为确定性行为.1)将Delphi内存管理器从https://github.com/pleriche/FastMM4更新到最新版本2)使用更新后的单元作为测试项目的第一个单元3)在FastMM4Options.inc中启用{.$ define AlwaysClearFreedMemory}选项4)构建您的项目,看看它现在是如何工作的,禁止使用免费内容 (2认同)