为什么程序在使用 const 字符串参数时崩溃



我的程序有以下代码:

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中崩溃,并且仅在优化处于打开状态时崩溃。如果优化为 OFF,程序将正常工作。可能是德尔菲虫吗?

涉及到 const 字符串参数时,有一些特殊的陷阱。
许多年前,我帮助一位同事解决了类似的特殊问题(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
  • 在这种情况下,写入时复制不适用,字符串将被重新分配。(很可能在不同的地址。
  • 因此,当该方法返回时,S 引用无效地址,并触发 AV。

对于这种情况:

 sa := s;

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

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

sa := AnsiString(s);

告诉编译器你只想获取指向字符串的指针,并且你知道如何使用此字符串引用。编译器不会干扰和烦恼你,但你要对正确的操作负责

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

我们今天调试了一个崩溃,这是一个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返回时,您使用的是释放的字符串。

等待发生的访问冲突。

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

AnsiString是一个指针。因此,您可以将其作为通常的字符串(这意味着字符数组(进行操作。崩溃是随机的,因此您可以随时因堆栈溢出或访问违规而获得崩溃,而不依赖于优化。

比你函数 FooBar 返回结果,sa 变量已经没有妈妈了。

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

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

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

最新更新