我的程序有以下代码:
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;