前几天我正和一位同事谈论如果你真的搞砸了,你怎么能在Delphi中泄漏字符串。默认情况下,字符串是引用计数和自动分配的,因此它们通常无需任何考虑即可工作 - 无需手动分配,大小计算或内存管理。
但我记得曾经读过有一种方法可以直接泄漏字符串(不包括在泄漏的对象中)。看起来它与通过引用传递字符串然后从传递给它的例程中的更大范围访问它有关。是的,我知道这很模糊,这就是为什么我在这里问这个问题。
答案 0 :(得分:7)
我不知道你的第二段中的问题,但是我在记录中被泄露的字符串咬了一次。
如果在包含字符串的记录上调用 FillChar(),则用零覆盖引用计数和动态分配的内存的地址。除非字符串为空,否则会泄漏内存。解决这个问题的方法是在清除记录占用的内存之前,在记录上调用 Finalize()。
不幸的是,当没有需要最终化的记录成员时,调用 Finalize()会导致编译器提示。碰巧我注意到 Finalize()调用以使提示静音,但后来当我在记录中添加了一个字符串成员时,我错过了取消注释该调用,因此引入了泄漏。幸运的是,我通常在调试模式下使用FastMM内存管理器处于最详细和偏执的设置,因此泄漏并没有被忽视。
编译器提示可能不是一件好事,默默地省略 Finalize()调用,如果不需要它会更好喔。
答案 1 :(得分:4)
不,我不认为会发生这样的事情。字符串变量可能会获得您不期望的值,但它不会泄漏内存。考虑一下:
var
Global: string;
procedure One(const Arg: string);
begin
Global := '';
// Oops. This is an invalid reference now. Arg points to
// what Global used to refer to, which isn't there anymore.
writeln(Arg);
end;
procedure Two;
begin
Global := 'foo';
UniqueString(Global);
One(Global);
Assert(Global = 'foo', 'Uh-oh. The argument isn''t really const?');
end;
这里One
的参数被声明为const,所以据说它不会改变。但是One
通过改变实际参数而不是形式参数来绕过它。过程Two
“知道”One
的参数是const,因此它希望实际参数保持其原始值。断言失败了。
该字符串尚未泄露,但此代码确实演示了如何为字符串获取悬空引用。 Arg
是Global
的本地别名。虽然我们已经更改Global
,但Arg
的值保持不变,并且因为它被声明为const,所以字符串的引用计数在进入函数时不会递增。重新分配Global
会将引用计数降为零,并且字符串已被销毁。将Arg
声明为var会产生同样的问题;通过它传递它将解决这个问题。 (对UniqueString
的调用只是为了确保字符串是引用计数的。否则,它可能是非引用计数的字符串文字。)所有编译器管理的类型都容易受到此问题的影响。简单的类型是免疫的。
泄漏字符串的唯一方法是将其视为字符串以外的其他方法,或者使用非类型感知的内存管理函数。 Mghie's answer描述了如何通过使用FillChar
来破坏字符串变量来将字符串视为字符串以外的字符串。非类型感知记忆功能包括GetMem
和FreeMem
。例如:
type
PRec = ^TRec;
TRec = record
field: string;
end;
var
Rec: PRec;
begin
GetMem(Rec, SizeOf(Rec^));
// Oops. Rec^ is uninitialized. This assignment isn't safe.
Rec^.field := IntToStr(4);
// Even if the assignment were OK, FreeMem would leak the string.
FreeMem(Rec);
end;
有两种方法可以修复它。一种是致电Initialize
和Finalize
:
GetMem(Rec, SizeOf(Rec^));
Initialize(Rec^);
Rec^.field := IntToStr(4);
Finalize(Rec^);
FreeMem(Rec);
另一种是使用类型感知功能:
New(Rec);
Rec^.field := IntToStr(4);
Dispose(Rec);
答案 2 :(得分:2)
实际上,在Delphi 2007和2009中,将字符串作为CONST或非const传递在引用计数方面是相同的。有一种情况,当字符串作为CONST传递时会导致访问冲突。这是问题一个
type
TFoo = class
S: string;
procedure Foo(const S1: string);
end;
procedure TFoo.Foo(const S1: string);
begin
S:= S1; //access violation
end;
var
F: TFoo;
begin
F:= TFoo.create;
try
F.S := 'S';
F.Foo(F.S);
finally
F.Free;
end;
end.
答案 3 :(得分:0)
我认为this可能与我的想法相似。它是字符串泄漏的反向,这是一个早期收集的字符串:
var
p : ^String;
procedure InitString;
var
s, x : String;
begin
s := 'A cool string!';
x := s + '. Append something to make a copy in' +
'memory and generate a new string.';
p := @x;
end;
begin
{ Call a function that will generate a string }
InitString();
{ Write the value of the string (pointed to by p) }
WriteLn(p^); // Runtime error 105!
{ Wait for a key press }
ReadLn;
end.
答案 4 :(得分:0)
泄漏字符串的另一种方法是将其声明为threadvar变量。有关详细信息,请参阅my question。有关解决方案,请参阅the solution on how to tidy it。