环境: Delphi 2007
我倾向于非常频繁地使用复杂的记录,因为它们提供了类的几乎所有优点,但处理起来却要简单得多。
总之,我刚刚实现的一个特别复杂的记录是破坏内存(后来导致了“无效指针操作”错误)。
这是内存垃圾代码的一个示例:
sSignature := gProfiles.Profile[_stPrimary].Signature.Formatted(True);第二次我称之为“无效指针操作”
如果我这样叫它的话,就可以了:
AProfile := gProfiles.Profile[_stPrimary];
ASignature := AProfile.Signature;
sSignature := ASignature.Formatted(True);背景代码:
gProfiles: TProfiles;
TProfiles = Record
private
FPrimaryProfileID: Integer;
FCachedProfile: TProfile;
...
public
< much code removed >
property Profile[ProfileType: TProfileType]: TProfile Read GetProfile;
end;
function TProfiles.GetProfile(ProfileType: TProfileType): TProfile;
begin
case ProfileType of
_stPrimary : Result := ProfileByID(FPrimaryProfileID);
...
end;
end;
function TProfiles.ProfileByID(iID: Integer): TProfile;
begin
<snip>
if LoadProfileOfID(iID, FCachedProfile) then
begin
Result := FCachedProfile;
end
else
...
end;
TProfile = Record
private
...
public
...
Signature: TSignature;
...
end;
TSignature = Record
private
public
PlainTextFormat : string;
HTMLFormat : string;
// The text to insert into a message when using this profile
function Formatted(bHTML: boolean): string;
end;
function TSignature.Formatted(bHTML: boolean): string;
begin
if bHTML then
result := HTMLFormat
else
result := PlainTextFormat;
< SNIP MUCH CODE >
end;好的,所以我有一个记录在记录中,这是接近盗梦空间级别混乱,我是第一个承认不是一个真正好的模型。显然,我将不得不重组它。我希望各位大师能更好地理解它为什么要破坏内存(与创建的字符串对象有关,然后释放.)这样我以后才能避免犯这样的错误。
谢谢
发布于 2010-12-22 02:59:15
您在类中使用记录的理由似乎存在缺陷。每次将记录作为函数结果返回或将记录作为函数参数传递或将记录变量从一个记录变量分配到另一个记录变量时,该记录结构的所有字段都将被复制到内存中。
仅这一点就值得关注。与引用类型相比,传递记录类型变量可以从程序中吸取生命。您的代码可以轻松地花费更多的时间从这里复制到那里,而不是实际完成工作。
在一条语句中串联调用三个函数与在单独的语句中调用这三个函数之间的区别在于中间结果的分配和生存期。在单独的语句中调用函数时,提供局部变量来保存调用之间的中间结果。变量是显式的,它们的寿命是很好的定义。
当在一条语句中调用函数时,编译器负责分配临时变量以保存调用之间的中间结果。对这些隐式变量的生命周期分析可能变得模糊--同一个局部变量能用来保存多个调用的中间结果吗?大多数情况下,答案可能是肯定的,但如果所涉及的记录类型包含编译器管理的数据类型(字符串、变体和接口)的字段,那么相同的局部变量不能只是被下一个数据块覆盖。
必须以有序的方式释放包含编译器托管类型的记录,以避免堆内存泄漏。如果这样的记录被垃圾数据覆盖,或者这样的记录在没有编译器意识的情况下被复制,那么编译器生成代码来在记录超出作用域时释放该记录的编译器托管字段,它很可能会报告它遇到了一个无效的指针和/或一个损坏的堆。
您的TSignature记录包含字符串字段,使其成为编译器管理的数据类型.只要有一个TSignature类型的局部变量,编译器就必须在函数正文中隐式地生成try..finally框架,以确保在执行离开该作用域时释放该局部变量结构中的字符串字段。
任何最终修改或重写TSignature记录中的字符串字段指针的操作都可能导致无效的指针操作错误。创建记录的副本(通过将其赋值给多个变量)应该会自动增加引用数,但是任何使用MemCopy将记录的内容批量复制到其他位置的操作都会抛出引用数,并在清理代码试图释放这些字符串字段的次数超过实际引用的次数时导致无效指针操作。将记录变量键入错误的记录类型可能会导致字符串字段被垃圾覆盖,并导致一行中无效的指针操作(当记录在作用域末尾被清除时)
还有一种可能性,即编译器本身在单语句场景中丢失了中间记录变量,并且正在清理隐藏的中间变量太多次或覆盖它们而不清理先前的值。在Delphi 3时代的某个地方,这个领域有一个编译器错误,但我不记得我们在哪个产品发行版中修复了它。我似乎还记得我所想到的与将记录类型函数结果传递给const类型参数有关的bug,因此它与您的场景并不完全匹配,但结果是相似的。
在将此报告为编译器错误之前,请在调试器反汇编视图中仔细检查您的代码。有很多方法可以自己搞砸这件事,可以看到中间结果在哪里分配、写入和由编译器生成的代码处理,以及您的代码是如何与该模式交互的。
当您看到一个temp记录变量的字符串字段被覆盖,而没有调用减少对这些字符串的引用时,就会出现冒烟的情况。它可能是由您的代码引起的,也可能是由编译器生成的代码中的某些东西引起的,但是唯一能找到答案的方法是亲眼目睹这一行为,并从中找出指摘。
发布于 2010-12-22 02:22:00
从您提供的代码中可以看出,腐败的发生并不明显,以下是一些建议。尝试不同的字段链接组合,看看是否可以复制它。
AProfile := gProfiles.Profile[_stPrimary];
sSignature := AProfile.Signature.Formatted(True);
ASignature := gProfiles.Profile[_stPrimary].Signature;
sSignature := ASignature.Formatted(True);如果您还没有打开范围检查和溢出检查。下载FastMM4并使用它的FullDebugMode。如果所有这些都不能给出答案,那么学习如何使用内存断点。
发布于 2010-12-22 06:48:53
有些东西我对你的代码提取不太好。TProfile是记录吗?因此,使用函数SomeName: TProfile会将记录内容复制到结果中,这是非常低效率的。即使使用了记录复制函数的优化版本,它仍然很费时。
您应该使用PProfile = ^TProfile类型通过引用/指针获得它。在这种情况下,您将防止大部分内存问题,即访问记录中的字符串。
但是您应该确保在整个TProfile指针的生命周期内,您的原始PProfile将保持在内存中可用。
在某些(罕见的)情况下,使用记录比使用类( 如果您解析一些二进制内容,例如 )更快/更容易。但是,您不应该使用普通记录类型来使用函数/方法来操作记录,而应该使用指向记录(或var参数)的指针。这样既安全又快。
https://stackoverflow.com/questions/4505485
复制相似问题