【问题标题】:Why does this one PAnsiChar get chopped when converted to AnsiString?为什么这个 PAnsiChar 在转换为 AnsiString 时会被截断?
【发布时间】:2016-12-15 23:47:18
【问题描述】:

请考虑以下程序:

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.

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

我想这与内存管理有关,但我没有足够的线索去寻找进行有意义的调查。谁能澄清一下?

【问题讨论】:

    标签: delphi delphi-2010 delphi-xe3


    【解决方案1】:

    对动态创建的字符串进行引用计数,并在没有引用时释放。

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

    导致创建一个临时的AnsiString,其地址通过转换为PAnsiChar,然后释放临时字符串。结果指针指向现在无人认领的内存,几乎可以出于任何原因覆盖该内存,包括在分配更多字符串期间。

    默认情况下,Delphi 和 FPC 都不会在释放期间清除内存,因此如果尚未重新使用内存,那么在读取曾经存在的内容时可能会很幸运。或者,如您所见,您可能不会。

    当像这样返回PAnsiChar时,你需要在调用者和被调用者之间就内存管理达成一致。您需要确保不要提前释放内存,并且您需要确保调用者知道之后如何释放内存。

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

    【讨论】:

    • 我可以理解这种推理,但我仍然觉得奇怪它是如何只发生在这些值之一的。它在我的示例程序中发生的方式与在我正在调试的程序中完全相同。例如,我希望它有时能工作,有时不能。什么可以解释这种一致性和价值观之间的差异?
    • @ThijsvanDien 分配器很难预测,但相当确定。如果它在您的程序的一次执行中以某种方式表现,那么它很有可能在所有执行中都以这种方式表现。另外一点是,'9999' 的长度是 4 的微不足道的倍数。内存块通常与 4 字节边界对齐,这解释了为什么部分覆盖恰好覆盖了第五个字符。
    • @Remy 不以句子开头的单词不应大写。返回指针的函数应该担心所有调用者,因此应该保持复数。您关于解除分配发生时间的具体说明是一个很好的说明,当我可以做一些额外的测试时,我会尝试将其恢复。
    • @Thijs:这是未记录的行为,所以任何事情都可能发生,您只是 幸运,其他函数调用显示了您的预期。行为未定义,但并非完全未确定。它仅仅取决于同一个内存管理器如何管理相同的分配和释放顺序,即使在不同的系统上也是如此。不同版本的编译器或不同的内存管理器可能会有所不同。
    • @ThijsvanDien - 尝试将随机行为转变为确定性行为。 1) 将 Delphi 内存管理器从 github.com/pleriche/FastMM4 更新到最新版本 2) 使用更新后的单元作为测试项目的第一个单元 3) 在 FastMM4Options.inc 中启用 {.$define AlwaysClearFreedMemory} 选项 4) 构建您的项目并查看现在如何运作,禁止在免费后使用内容
    猜你喜欢
    • 1970-01-01
    • 2010-10-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-30
    • 2018-10-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多