使用复杂记录时"无效指针操作"的建议

Zax*_*Zax 2 delphi records delphi-2007

环境:德尔福2007

<理由>我倾向于经常使用复杂的记录,因为它们几乎提供了类的所有优点,但处理起来更简单.</ Justification>

Anyhoo,我刚刚实现的一个特别复杂的记录是垃圾内存(后来导致"无效的指针操作"错误).

这是内存垃圾代码的一个例子:

sSignature := gProfiles.Profile[_stPrimary].Signature.Formatted(True);
Run Code Online (Sandbox Code Playgroud)

在我第二次调用它时,我得到"无效的指针操作"

如果我这样称呼它可以正常工作:

  AProfile    := gProfiles.Profile[_stPrimary];
  ASignature  := AProfile.Signature;
  sSignature  := ASignature.Formatted(True);
Run Code Online (Sandbox Code Playgroud)

背景代码:

  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;
Run Code Online (Sandbox Code Playgroud)

好吧,所以我在记录中的记录中有一条记录,即接近初始级别的混乱,我是第一个承认并不是真正好的模型.显然,我将不得不对其进行重组.我希望你从大师那里更好地理解为什么它会破坏内存(与创建然后释放的字符串对象有关...)以便我可以避免在将来犯这些错误.

谢谢

dth*_*rpe 9

你在课堂上使用记录的理由似乎有缺陷.每次将记录作为函数结果返回或将记录作为函数参数传递或从一个记录var分配给另一个记录var时,该记录结构的所有字段都将被复制到内存中.

仅此一点可能引起关注.与引用类型相比,传递记录类型变量可以从程序中榨取生命.您的代码可以轻松地花费更多时间从这里到那里复制东西,而不是实际完成工作.

在一个语句中串联调用三个函数与在单独的语句中调用三个函数之间的区别在于中间结果的分配和生命周期.在单独的语句中调用函数时,可以提供局部变量来保存调用之间的中间结果.变量是显式的,它们的生命周期定义明确.

在一个语句中调用函数时,编译器负责分配临时变量以保存调用之间的中间结果.这些隐式变量的生命周期分析可能变得模糊 - 可以使用相同的局部变量来保存多个调用的中间结果吗?大多数情况下,答案可能是肯定的,但如果涉及的记录类型包含编译器管理的数据类型(字符串,变体和接口)字段,则不能仅使用下一个数据块覆盖相同的局部变量.

必须以有序的方式处理包含编译器管理类型的记录,以避免泄漏堆内存.如果这样的记录被垃圾数据覆盖,或者如果这样的记录没有编译器的认知复制,则编译器生成的代码来处理记录的编译器管理领域的当记录超出范围可能会报告它遇到无效指针和/或损坏的堆.

您的TSignature记录包含字符串字段,使其成为编译器管理的数据类型.无处不在,你有型TSignature的局部变量,编译器隐式生成try..finally在函数体框架,以确保在局部变量结构的字符串字段得到释放,当执行离开该范围.

最终修改或覆盖TSignature记录中的字符串字段指针的任何操作都可能导致无效指针操作错误.使记录的副本(通过将其分配给多个变量)应该自动递增裁判计数,但任何使用MEMCOPY的批量记录的内容复制到其他位置会甩开裁判计数,并导致无效指针操作时清理代码尝试释放这些字符串字段的次数比实际引用的次数多.将记录变量类型化为错误的记录类型可能会导致字符串字段被垃圾覆盖,并导致无效的指针操作在线下(当在范围的末尾清理记录时)

还有可能编译器本身在单语句场景中丢失了对中间记录变量的跟踪,并且正在清理隐藏的中间体太多次或者在不清除先前值的情况下覆盖它们.有这方面的地方早在德尔福3时代一个编译器错误,但我不记得哪个产品版本中,我们在固定它.我似乎记得这个错误我已经考虑到所涉记录类型的函数结果传递给const型参数,因此它与您的方案不完全匹配,但后果类似.

在将此报告为编译器错误之前,请在调试器反汇编视图中使用细齿梳查看代码. 有很多方法可以让你自己解决这个问题. 查看编译器生成的代码分配,写入和处理中间结果的位置,以及代码与该模式的交互方式.

当你看到一个临时记录变量的字符串字段被覆盖而没有调用减少对这些字符串的引用时,就会出现吸烟枪.它可能是由您的代码引起的,或者它可能是由编译器生成的代码中的某些内容引起的,但唯一可以找到的方法是见证该行为并从那里找出指针.