在快速绘画周期中使用释放的对象的例外
总结:
对于 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
该代码
是一个错误。您应该通过引用传递
aRotationMatUser、aTranslationMatUser、aScalingMatUser
参数:您应该使用 var 在上述过程中传递参数,因为没有它
堆栈变量,并且它不会
感觉;
值到临时堆栈
变量,以及结果记忆
泄漏。
为什么错误的代码有时可以正常工作(甚至可能不会导致内存泄漏)的问题是另一个故事。
再进行一次编辑
正如您已经提到的,Delphi 对象是一个引用。所以你不需要使用
var
来改变对象。但您的过程不同 - 它更改引用本身,而不仅仅是这些引用指向的数据,因此您应该通过引用传递这些引用(aRotationMatUser、aTranslationMatUser、aScalingMatUser
)。这就是为什么你需要var
。The code
is an error. You should pass
aRotationMatUser, aTranslationMatUser, aScalingMatUser
parameters by reference:You should use var to pass the arguments in the above procedure because without it
stack variables, and it makes no
sense;
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 needvar
.