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

时间:2014-04-29 10:39:56

标签: string delphi memory crash 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中崩溃且仅在优化为ON时才会崩溃。如果优化为OFF,程序将正常工作。它可能是Delphi的错误吗?

5 个答案:

答案 0 :(得分:3)

对于 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。

答案 1 :(得分:2)

对于这种情况:

 sa := s;

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

对于硬类型转换的情况(虽然类型相同)

sa := AnsiString(s);

你告诉编译器你只想获得指向字符串的指针,你知道如何使用这个字符串引用。编译器不会干扰和骚扰您,但您负责正确的操作

P.S。我无法用Delphi XE5重现这个问题 - 简单的赋值和类型转换会导致LS调用LStrLAsg(内部函数)。 (当然,编译器魔法可能会有所改变)

答案 2 :(得分:2)

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

等待发生的访问冲突。

或者,就客户而言:正在发生。

答案 3 :(得分:0)

因为AnsiString是一个指针。所以,你不能把它作为一个通常的字符串(这意味着一系列字符)来操作。崩溃是随机的,因此您可以随时获得堆栈溢出或访问冲突的结果,而不是依赖于优化。

比你的函数FooBar返回结果,一个变量已经从mamory中释放。

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

答案 4 :(得分:0)

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

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