【问题标题】:Why does the program crash when using const string arguments?为什么使用 const 字符串参数时程序会崩溃?
【发布时间】:2014-06-15 06:03:04
【问题描述】:

我的程序有以下代码:

function FooBar(const s: string): string;
var
  sa: AnsiString;
begin

  // ..........................

  sa := AnsiString(s);
  sa := AnsiString(StringReplace(string(sa), '*', '=', [rfReplaceAll]));
  sa := AnsiString(StringReplace(string(sa), ' ', '+', [rfReplaceAll]));
  result := string(sa);

  // ..........................

end;

我注意到程序确实在“某处”崩溃了,FastMM4 说我写入了一个已释放的对象。一旦我注释掉“const”,程序就可以工作了。

我已阅读有关 const 参数的 Delphi 文档,但我无法弄清楚为什么 const 参数会使程序崩溃。我很想了解它。

更新:程序只会在 Delphi 6 中崩溃,并且只有在优化开启时才会崩溃。如果优化关闭,程序将正常工作。可能是 Delphi 的错误?

【问题讨论】:

  • 你可以试试没有 sa 像这样:Result:=AnsiString(s);Result:=AnsiString(StringReplace(string(Result),...?
  • 为什么在Delphi6中使用这些转换,默认情况下string = AnsiString?
  • 因为我正在编写一个必须由 Delphi 6 和 XE4 使用的组件。我不想在我的代码中出现任何警告,所以我明确地转换了它们(而且这个函数只做 Base64 的东西,所以转换是可以的,不会影响 Unicode 能力)。
  • 只使用字符串。这里不需要转换。 StringReplace 将起作用。
  • 如果您从该函数中删除所有类型转换和所有提及AnsiString,那么您将不会收到任何隐式转换警告,因为不会有任何隐式转换。跨度>

标签: string delphi memory crash constants


【解决方案1】:

因为 AnsiString 是一个指针。所以,你不能把它当作一个普通的字符串(这意味着一个字符数组)来操作。崩溃是随机的,因此您可以随时获得由于 stackoverflow 或访问冲突而导致的崩溃,而不依赖于优化。

比你函数 FooBar 返回结果,sa 变量已经从 mamory 中释放出来了。

尝试使用 PChar 或 PAnsiChar 并根据需要为这些变量分配内存。

【讨论】:

  • 我了解到 Pascal 字符串使用自动引用计数,因此我不必关心分配/释放内存。 Delphi 6中的“string”和“AnsiString”有什么不同吗?我认为他们是平等的。
  • 您使用的是 Delphi 6 还是 Delphi XE6?
【解决方案2】:

由于转换为 AnsiString 并返回此处没有意义,因此我将其重写为

function FooBar(const s:string):string;
begin
  Result:=
    StringReplace(
    StringReplace(
      s
      ,'*','=',[rfReplaceAll])
      ,' ','+',[rfReplaceAll]);
end;

【讨论】:

  • 是的。但我的问题是,要理解,为什么是“sa := s”。 “const”使程序崩溃。为什么自动引用计数不起作用?
  • 因为优化与分配给本地变量和强制转换相混淆。
  • @StijnSanders:我认为分配给本地变量和演员表并不重要。 ISTM 认为这只是 D6 中的一个错误。
【解决方案3】:

对于这种情况:

 sa := s;

自动引用计数 (ARC) 有效。这是惯用的方式,编译器知道如何使用这些字符串 - 如果 sa 会改变,它会创建新的副本等等。

对于硬类型转换的情况(尽管类型相同)

sa := AnsiString(s);

你告诉编译器你只想得到指向字符串的指针,并且你知道如何使用这个字符串引用。编译器不会干扰和烦扰你,但你有责任正确操作

附:我无法重现 Delphi XE5 的问题 - 简单的分配和类型转换都会导致 LStrLAsg(内部函数)调用 ARC。 (当然,编译器的魔法可以稍微改变一下)

【讨论】:

  • 非常感谢您提供的有用提示。那么,我是否理解正确,“sa := AnsiString(s)”将使 ARC 计数器为 1(无论 s 是定义为“const s”还是只是“s”?)并且由于局部变量"sa" 然后被清除,s 也会被清除(因为它们在同一个地址),这会导致调用者对 s 有非法引用?
  • ISTM 这只是 D6 中的一个错误。它不再存在于当前版本中。我查看了 XE4 中的代码,并且处理得当。我不认为演员阵容是问题。编译器应该能够处理(或忽略)这些。
  • 我在某处读到“const”在较新的 Delphi 版本中失去了功能(参见stackoverflow.com/a/1134704/3544341,Andreas Hausladen 的第二条评论)。这可能是它适用于较新版本的原因。
  • 我将此答案掩盖为解决方案,即使其他答案也是正确的,因为我在原始帖子中的代码有许多不同的问题(例如返回局部变量)。但是,您提到了一个我不知道的非常有趣的事实(类型转换不会增加 ARC 计数器)。
【解决方案4】:

const string 参数有一些特殊的陷阱。
许多年前,我帮助一位同事解决了一个类似的特殊问题(D3 iirc)。以下简化示例看起来不像您的具体问题,但它可能会给您一些想法:

type
  TMyClass
    FString: string;
    procedure AppendString(const S: string);
  end;

procedure TMyClass.AppendString;
begin
  FString := FString + S;
end;

现在,如果您有一个TMyClass 的实例并尝试调用AppendString(FString); 来加倍字符串,您可能会遇到访问冲突。 (如果你这样做,还有一些其他因素会影响。)原因如下:

  • const 防止在方法调用中对字符串进行引用计数。
  • 所以FString 的值改变时可能有refCount = 1
  • 在这种情况下,Copy-on-Write 不适用,字符串被重新分配。 (很可能在不同的地址。)
  • 所以当方法返回时,S 引用了一个无效的地址,并触发了一个 AV。

【讨论】:

  • 当前版本的 _USrCat(附加字符串时调用的 RTL 例程)知道这一点,不会使用错误的地址。我身边没有 D6,所以它可能还没有检查这个。
【解决方案5】:

我们今天调试了一个崩溃,这是一个 Delphi 5 编译器代码生成错误:

procedure TForm1.Button1Click(Sender: TObject);
var
   s: string;
begin
   s := 'Hello, world! '+IntToStr(7);
   DoSomething(s);

   //String s now has a reference count of zero, and has already been freed
   ShowMessage(s);
end;

procedure TForm1.DoSomething(const Title: string);
var
    s: AnsiString;
begin
    s := AnsiString(Title);

    if Now = 7 then
        OutputDebugString('This is a string that is irrelevant');
end;

Delphi 5 编译器错误地尝试减少两者的引用计数

  • Title
  • s

并导致const 字符串的引用计数变为零。这导致它被释放。所以当DoSomething 返回时,你使用的是一个释放的字符串。

等待发生的访问冲突。

或者,在客户的情况下:正在发生。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-05
    • 2014-04-30
    • 1970-01-01
    相关资源
    最近更新 更多