在快速绘画周期中使用释放的对象的例外

发布于 2024-10-20 01:36:07 字数 8182 浏览 5 评论 0原文

总结:
对于 Delphi 函数/过程,如果类的实例作为参数传递,则会在临时调用堆栈上创建另一个引用(除了原始引用之外)以指向该实例并在本地使用。因此,请注意:

(1) 如果函数/过程只想更改该实例的内容/字段/属性,则不需要 var 前缀;

(2) 如果函数/过程可能想要将引用重新分配给新实例,请使用 var 前缀,否则将重新分配临时引用。

(3) 注意,如果函数/过程重新分配引用并且不使用 var 前缀,结果可能是正确的,这甚至更糟,因为最终代码有一天会崩溃。

=========================================
情况是:
这是一个小应用程序。 TMolForm 是一个MDIChild 表单,每个TMolForm 都包含一个TMolScene,它源自TPaintBox。 TMolScene 绘制 TMol。在TMolScene的绘制过程中,如果TMolScene的大小发生改变,TMolScene会调用TMol.Rescale。然后TMolScene调用TMol.TransformCooperatives为后续渲染建立坐标。

问题是:
现在,在 TMol.Rescale 中,我重置了调用者 TMolScene 传递的矩阵。然而,我遇到了一些例外,我想不出原因。

(1)具体来说,如果我有多个 TMolForm,并且快速调整大小、鼠标拖动(也就是分子旋转)、在TMolForm之间切换,不到5分钟,突然传递到 TMol.TransformCooperatives 的矩阵(据说已经在 TMol.Rescale 中重置)为零或包含零内容。

(2) 如果我启用 FastMM4 及其 FullDebugMode,并重复上述鼠标移动,我可以获得“TMol.Rescale attempts to free a freed object”。当最后一次调用(或最后一个绘制周期)未完成时,TMol.Rescale 似乎再次被调用。我的意思是,我没有进行任何涉及多线程的尝试,当最后一次调用尚未返回时,TMol.Rescale 怎么可能被第二次调用? 我完全迷失了。您能帮忙评论一下任何可能的原因吗?

(3) 如果我将矩阵重置从 TMol.Rescale 中删除并放入其调用者 TMolScene.OnScenePaint 中,则异常似乎不会发生,至少在 5 分钟内不会发生。 (我没有快速滥用鼠标超过5分钟。也许还有其他更好的测试方法。)我不知道为什么这会起作用以及为什么上面的崩溃有时

(4)如果我只有一个TMolform,上述异常似乎不会发生,至少在5分钟内不会发生。

我必须承认,我编写了以下最小化代码以捕获异常。然而,尽管执行过程应该反映真实情况,但异常不会发生。如果您想查看真实的代码,我愿意通过电子邮件或其他方式发送给您。不过,这是爱好,写得不好,抱歉。

任何建议,无论是关于例外情况还是关于不良编码习惯的建议,我们都非常感激。

        unit uMolForm;

        interface

        uses
          Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
          ExtCtrls, Dialogs;

        type
          TVec = class;
          TMat = class;
          TMol = class;
          TMolScene = class;
          TMolForm = class;

          TVec = class
          public
            X, Y, Z: Extended;
            constructor Create; overload;
            constructor Create(aX, aY, aZ: Extended); overload;
          end;

          TMat = class
          private
            FX, FY, FZ, FT: TVec;
          public
            property X: TVec read FX;
            property Y: TVec read FY;
            property Z: TVec read FZ;
            constructor Create;
            destructor Destroy; override;
            function ToUnit: TMat;
          end;

          TMol = class
          public
            constructor Create;
            destructor Destroy; override;
            procedure Rescale(aBbWidth, aBbHeight: Integer;
              aRotationMatUser, aTranslationMatUser, aScalingMatUser: TMat);
            procedure TransformCoordinates(aBbWidth, aBbHeight: Integer;
              aRotationMatUser, aTranslationMatUser, aScalingMatUser: TMat);
          end;

          TMolScene = class(TPaintBox)
          private
            FBbWidth, FBbHeight: Integer;
            FRotationMat, FTranslationMat, FScalingMat: TMat;
            FMol: TMol;
            procedure OnScenePaint(Sender: TObject);
            procedure OnSceneMouseDown(Sender: TObject; Button: TMouseButton;
              Shift: TShiftState; X, Y: Integer);
            procedure OnSceneMouseUp(Sender: TObject; Button: TMouseButton;
              Shift: TShiftState; X, Y: Integer);
            procedure OnSceneMouseMove(Sender: TObject; Shift: TShiftState;
              X, Y: Integer);
          public
            constructor Create(AOwner: TComponent);
            destructor Destroy; override;
          end;

          TMolForm = class(TForm)
            procedure FormClose(Sender: TObject; var Action: TCloseAction);
            procedure FormCreate(Sender: TObject);
          private
            { Private declarations }
            FMolScene: TMolScene;
          public
            { Public declarations }
          end;

        implementation

        {$R *.dfm}

        { TVec }

        constructor TVec.Create;
        begin
          inherited;

          X := 0;
          Y := 0;
          Z := 0;
        end;

        constructor TVec.Create(aX, aY, aZ: Extended);
        begin
          inherited Create;

          X := aX;
          Y := aY;
          Z := aZ;
        end;

        { TMat }

        constructor TMat.Create;
        begin
          inherited;

          ToUnit;
        end;

        destructor TMat.Destroy;
        begin
          FreeAndNil(FX);
          FreeAndNil(FY);
          FreeAndNil(FZ);
          FreeAndNil(FT);

          inherited;
        end;

        function TMat.ToUnit: TMat;
        begin
          FreeAndNil(FX);
          FreeAndNil(FY);
          FreeAndNil(FZ);
          FreeAndNil(FT);

          FX := TVec.Create(1, 0, 0);
          FY := TVec.Create(0, 1, 0);
          FZ := TVec.Create(0, 0, 1);
          FT := TVec.Create;

          Result := Self;
        end;

        { TMol }

        constructor TMol.Create;
        begin
          inherited;

        end;

        destructor TMol.Destroy;
        begin

          inherited;
        end;

        procedure TMol.Rescale(aBbWidth, aBbHeight: Integer;
          aRotationMatUser, aTranslationMatUser, aScalingMatUser: TMat);
        begin

          FreeAndNil(aRotationMatUser);
          FreeAndNil(aTranslationMatUser);
          FreeAndNil(aScalingMatUser);

          aRotationMatUser := TMat.Create;
          aTranslationMatUser := TMat.Create;
          aScalingMatUser := TMat.Create;
        end;

        procedure TMol.TransformCoordinates(aBbWidth, aBbHeight: Integer;
          aRotationMatUser, aTranslationMatUser, aScalingMatUser: TMat);
        begin

          if (aRotationMatUser.X = nil) or (aRotationMatUser.Y = nil) or
            (aRotationMatUser.Z = nil) or (aTranslationMatUser.X = nil) or
            (aTranslationMatUser.Y = nil) or (aTranslationMatUser.Z = nil) or
            (aScalingMatUser.X = nil) or (aScalingMatUser.Y = nil) or
            (aScalingMatUser.Z = nil) then
          begin
            raise Exception.Create('what happened?!');
          end;
        end;

        { TMolScene }

        constructor TMolScene.Create(AOwner: TComponent);
        begin
          inherited;

          FRotationMat := TMat.Create;
          FTranslationMat := TMat.Create;
          FScalingMat := TMat.Create;
          FMol := TMol.Create;

          Self.OnPaint := Self.OnScenePaint;
          Self.OnMouseDown := Self.OnSceneMouseDown;
          Self.OnMouseUp := Self.OnSceneMouseUp;
          Self.OnMouseMove := Self.OnSceneMouseMove;
        end;

        destructor TMolScene.Destroy;
        begin
          FreeAndNil(FRotationMat);
          FreeAndNil(FTranslationMat);
          FreeAndNil(FScalingMat);
          FreeAndNil(FMol);

          inherited;
        end;

        procedure TMolScene.OnScenePaint(Sender: TObject);
        begin
          if (FBbWidth <> Self.ClientWidth) or (FBbHeight <> Self.ClientHeight) then
          begin
            FBbWidth := Self.ClientWidth;
            FBbHeight := Self.ClientHeight;
            FMol.Rescale(FBbWidth, FBbHeight, FRotationMat, FTranslationMat,
              FScalingMat);
          end;

          FMol.TransformCoordinates(FBbWidth, FBbHeight, FRotationMat, FTranslationMat,
            FScalingMat);
        end;

        procedure TMolScene.OnSceneMouseDown(Sender: TObject; Button: TMouseButton;
          Shift: TShiftState; X, Y: Integer);
        begin
          Self.Repaint;
        end;

        procedure TMolScene.OnSceneMouseUp(Sender: TObject; Button: TMouseButton;
          Shift: TShiftState; X, Y: Integer);
        begin
          Self.Repaint;
        end;

        procedure TMolScene.OnSceneMouseMove(Sender: TObject; Shift: TShiftState; X,
          Y: Integer);
        begin
          Self.Repaint;
        end;

        { TMolForm }

        procedure TMolForm.FormCreate(Sender: TObject);
        begin
          FMolScene := TMolScene.Create(Self);
          FMolScene.Parent := Self;
          FMolScene.Align := alClient;
        end;

        procedure TMolForm.FormClose(Sender: TObject; var Action: TCloseAction);
        begin
          Action := caFree;
        end;

        end.

Summarization:
For a Delphi function/procedure, if an instance of a class is passed through as an argument, another reference (besides the original reference) is created on temporary calling stack to point to that instance and is used locally. Thus, be careful:

(1) If the function/procedure only wants to change the content/fields/properties of that instance, no var prefix is needed;

(2) If the function/procedure probably wants to re-assign the reference to a new instance, use var prefix, or it is the temporary reference that gets re-assigned.

(3) Note, if the function/procedure re-assigns the reference and the var prefix is not used, the outcome is probably right, which is even worse, because eventually the code will break some day.

=======================================
The situation is:
It is a small application. The TMolForm is a MDIChild Form, and every TMolForm contains a TMolScene, which descends from TPaintBox. The TMolScene draws the TMol. In the painting procedure of TMolScene, TMolScene calls TMol.Rescale if TMolScene is resized. Then TMolScene calls TMol.TransformCoordinates to build up coordinates for the subsequent rendering.

The problem is:
Now, in TMol.Rescale, I reset the matrices passed through by the caller, TMolScene. However, I meet exceptions which I cannot think of the reason.

(1) Specifically, if I have multiple TMolForm, and rapidly do resizing, mouse dragging (which is molecule rotating), switching between TMolForm, in less than 5 minutes, suddenly the matrices (supposedly already resetted in TMol.Rescale) passed into TMol.TransformCoordinates are nil or contain nil content.

(2) If I enable FastMM4 and its FullDebugMode, and repeat the above mouse movements, I can get "TMol.Rescale attempts to free a freed object". It seems TMol.Rescale is called again when the last call (or the last paint cycle) is not finished. I mean, I didn't make any attempts involving multi-threading, how possibly could TMol.Rescale be call the second time when the last call does not return yet?
I am completely lost. Could you help to comment on any possible reasons?

(3) If I remove the resetting of matrices out of TMol.Rescale and into its caller, TMolScene.OnScenePaint, the exceptions seem not to happen, at least not in 5 minutes. (I did not rapidly abusing the mouse longer than 5 minutes. Perhaps there is other better way of testing.) I have no clue why this works and why the above crashes sometimes.

(4) If I only have one TMolform, the above exceptions seem not to happen, at least not in 5 minutes.

I must admit that I made up the following minimized code in order to catch the exceptions. However, although the execution procedure should mirror the real situation, the exceptions don't occur. If you would like to see the real code I am willing to send to you through email or something else. It is hobby and not well written, though, sorry.

Any suggestions, either on the exceptions, or on bad coding habits, are really appreciated.

        unit uMolForm;

        interface

        uses
          Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
          ExtCtrls, Dialogs;

        type
          TVec = class;
          TMat = class;
          TMol = class;
          TMolScene = class;
          TMolForm = class;

          TVec = class
          public
            X, Y, Z: Extended;
            constructor Create; overload;
            constructor Create(aX, aY, aZ: Extended); overload;
          end;

          TMat = class
          private
            FX, FY, FZ, FT: TVec;
          public
            property X: TVec read FX;
            property Y: TVec read FY;
            property Z: TVec read FZ;
            constructor Create;
            destructor Destroy; override;
            function ToUnit: TMat;
          end;

          TMol = class
          public
            constructor Create;
            destructor Destroy; override;
            procedure Rescale(aBbWidth, aBbHeight: Integer;
              aRotationMatUser, aTranslationMatUser, aScalingMatUser: TMat);
            procedure TransformCoordinates(aBbWidth, aBbHeight: Integer;
              aRotationMatUser, aTranslationMatUser, aScalingMatUser: TMat);
          end;

          TMolScene = class(TPaintBox)
          private
            FBbWidth, FBbHeight: Integer;
            FRotationMat, FTranslationMat, FScalingMat: TMat;
            FMol: TMol;
            procedure OnScenePaint(Sender: TObject);
            procedure OnSceneMouseDown(Sender: TObject; Button: TMouseButton;
              Shift: TShiftState; X, Y: Integer);
            procedure OnSceneMouseUp(Sender: TObject; Button: TMouseButton;
              Shift: TShiftState; X, Y: Integer);
            procedure OnSceneMouseMove(Sender: TObject; Shift: TShiftState;
              X, Y: Integer);
          public
            constructor Create(AOwner: TComponent);
            destructor Destroy; override;
          end;

          TMolForm = class(TForm)
            procedure FormClose(Sender: TObject; var Action: TCloseAction);
            procedure FormCreate(Sender: TObject);
          private
            { Private declarations }
            FMolScene: TMolScene;
          public
            { Public declarations }
          end;

        implementation

        {$R *.dfm}

        { TVec }

        constructor TVec.Create;
        begin
          inherited;

          X := 0;
          Y := 0;
          Z := 0;
        end;

        constructor TVec.Create(aX, aY, aZ: Extended);
        begin
          inherited Create;

          X := aX;
          Y := aY;
          Z := aZ;
        end;

        { TMat }

        constructor TMat.Create;
        begin
          inherited;

          ToUnit;
        end;

        destructor TMat.Destroy;
        begin
          FreeAndNil(FX);
          FreeAndNil(FY);
          FreeAndNil(FZ);
          FreeAndNil(FT);

          inherited;
        end;

        function TMat.ToUnit: TMat;
        begin
          FreeAndNil(FX);
          FreeAndNil(FY);
          FreeAndNil(FZ);
          FreeAndNil(FT);

          FX := TVec.Create(1, 0, 0);
          FY := TVec.Create(0, 1, 0);
          FZ := TVec.Create(0, 0, 1);
          FT := TVec.Create;

          Result := Self;
        end;

        { TMol }

        constructor TMol.Create;
        begin
          inherited;

        end;

        destructor TMol.Destroy;
        begin

          inherited;
        end;

        procedure TMol.Rescale(aBbWidth, aBbHeight: Integer;
          aRotationMatUser, aTranslationMatUser, aScalingMatUser: TMat);
        begin

          FreeAndNil(aRotationMatUser);
          FreeAndNil(aTranslationMatUser);
          FreeAndNil(aScalingMatUser);

          aRotationMatUser := TMat.Create;
          aTranslationMatUser := TMat.Create;
          aScalingMatUser := TMat.Create;
        end;

        procedure TMol.TransformCoordinates(aBbWidth, aBbHeight: Integer;
          aRotationMatUser, aTranslationMatUser, aScalingMatUser: TMat);
        begin

          if (aRotationMatUser.X = nil) or (aRotationMatUser.Y = nil) or
            (aRotationMatUser.Z = nil) or (aTranslationMatUser.X = nil) or
            (aTranslationMatUser.Y = nil) or (aTranslationMatUser.Z = nil) or
            (aScalingMatUser.X = nil) or (aScalingMatUser.Y = nil) or
            (aScalingMatUser.Z = nil) then
          begin
            raise Exception.Create('what happened?!');
          end;
        end;

        { TMolScene }

        constructor TMolScene.Create(AOwner: TComponent);
        begin
          inherited;

          FRotationMat := TMat.Create;
          FTranslationMat := TMat.Create;
          FScalingMat := TMat.Create;
          FMol := TMol.Create;

          Self.OnPaint := Self.OnScenePaint;
          Self.OnMouseDown := Self.OnSceneMouseDown;
          Self.OnMouseUp := Self.OnSceneMouseUp;
          Self.OnMouseMove := Self.OnSceneMouseMove;
        end;

        destructor TMolScene.Destroy;
        begin
          FreeAndNil(FRotationMat);
          FreeAndNil(FTranslationMat);
          FreeAndNil(FScalingMat);
          FreeAndNil(FMol);

          inherited;
        end;

        procedure TMolScene.OnScenePaint(Sender: TObject);
        begin
          if (FBbWidth <> Self.ClientWidth) or (FBbHeight <> Self.ClientHeight) then
          begin
            FBbWidth := Self.ClientWidth;
            FBbHeight := Self.ClientHeight;
            FMol.Rescale(FBbWidth, FBbHeight, FRotationMat, FTranslationMat,
              FScalingMat);
          end;

          FMol.TransformCoordinates(FBbWidth, FBbHeight, FRotationMat, FTranslationMat,
            FScalingMat);
        end;

        procedure TMolScene.OnSceneMouseDown(Sender: TObject; Button: TMouseButton;
          Shift: TShiftState; X, Y: Integer);
        begin
          Self.Repaint;
        end;

        procedure TMolScene.OnSceneMouseUp(Sender: TObject; Button: TMouseButton;
          Shift: TShiftState; X, Y: Integer);
        begin
          Self.Repaint;
        end;

        procedure TMolScene.OnSceneMouseMove(Sender: TObject; Shift: TShiftState; X,
          Y: Integer);
        begin
          Self.Repaint;
        end;

        { TMolForm }

        procedure TMolForm.FormCreate(Sender: TObject);
        begin
          FMolScene := TMolScene.Create(Self);
          FMolScene.Parent := Self;
          FMolScene.Align := alClient;
        end;

        procedure TMolForm.FormClose(Sender: TObject; var Action: TCloseAction);
        begin
          Action := caFree;
        end;

        end.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

想念有你 2024-10-27 01:36:07

该代码

    procedure TMol.Rescale(aBbWidth, aBbHeight: Integer;
      aRotationMatUser, aTranslationMatUser, aScalingMatUser: TMat);
    begin

      FreeAndNil(aRotationMatUser);
      FreeAndNil(aTranslationMatUser);
      FreeAndNil(aScalingMatUser);

      aRotationMatUser := TMat.Create;
      aTranslationMatUser := TMat.Create;
      aScalingMatUser := TMat.Create;
    end;

是一个错误。您应该通过引用传递 aRotationMatUser、aTranslationMatUser、aScalingMatUser 参数:

    procedure TMol.Rescale(aBbWidth, aBbHeight: Integer;
      **var** aRotationMatUser, aTranslationMatUser, aScalingMatUser: TMat);

您应该使用 var 在上述过程中传递参数,因为没有它

  • FreeAndNil 'nilles' 临时
    堆栈变量,并且它不会
    感觉;
  • 构造函数调用分配
    值到临时堆栈
    变量,以及结果记忆
    泄漏。

为什么错误的代码有时可以正常工作(甚至可能不会导致内存泄漏)的问题是另一个故事。


再进行一次编辑

正如您已经提到的,Delphi 对象是一个引用。所以你不需要使用var来改变对象。但您的过程不同 - 它更改引用本身,而不仅仅是这些引用指向的数据,因此您应该通过引用传递这些引用(aRotationMatUser、aTranslationMatUser、aScalingMatUser)。这就是为什么你需要var

The code

    procedure TMol.Rescale(aBbWidth, aBbHeight: Integer;
      aRotationMatUser, aTranslationMatUser, aScalingMatUser: TMat);
    begin

      FreeAndNil(aRotationMatUser);
      FreeAndNil(aTranslationMatUser);
      FreeAndNil(aScalingMatUser);

      aRotationMatUser := TMat.Create;
      aTranslationMatUser := TMat.Create;
      aScalingMatUser := TMat.Create;
    end;

is an error. You should pass aRotationMatUser, aTranslationMatUser, aScalingMatUser parameters by reference:

    procedure TMol.Rescale(aBbWidth, aBbHeight: Integer;
      **var** aRotationMatUser, aTranslationMatUser, aScalingMatUser: TMat);

You should use var to pass the arguments in the above procedure because without it

  • FreeAndNil 'nilles' the temporary
    stack variables, and it makes no
    sense;
  • constructor calls assign
    values to the temporary stack
    variables, with resulting memory
    leaks.

The question why the erroneous code sometimes work right (and probably does not even cause memory leaks) is a different story.


One more edit

As you already mentioned a Delphi object is a reference. So you need not use var to change the object. But your procedure is different - it changes the references themselves, not only the data pointed by these references, so you should pass these references (aRotationMatUser, aTranslationMatUser, aScalingMatUser) by reference. That is why you need var.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文