delphi 在将一些数据输入到动态多维数组后关闭应用程序时,不断获得AV

ehxuflar  于 2023-03-01  发布在  其他
关注(0)|答案(2)|浏览(97)

我在我的一个项目中遇到了一些奇怪的问题。最奇怪的是,这只发生在这个项目中,我不能在另一个项目中重现它。
幸运的是,这是一个小项目(旨在提供一个答案的问题之一,在这里的SO -仍然没有完成),所以我设法弄清楚,它必须做一些销毁TIimage组件,我已经放在我的形式在设计时,并设置一个BMP图像,它也在设计时。
我得到的AV是:项目Project1.exe引发异常类$C0000005,并显示消息“0x 00407430处的访问冲突:读取地址0xfffffffc“。
最后三个调用堆栈是:
Vcl.Graphics.TBitmapCanvas.Destroy
Vcl.Graphics.TCanvasDestroy
System.TObject.Free
Delphi 也把我放在TObject.Free方法中的System.pas单元中,在线“销毁”。

procedure TObject.Free;
begin
  if Self <> nil then
{$IFDEF AUTOREFCOUNT}
    __ObjRelease;
{$ELSE}
    Destroy;
{$ENDIF}
end;

此时的Self值仅显示为()。
但是为什么这个反病毒只发生在我把一些数据存储到多维数组中的时候。
我可以在运行时创建和设置多维数组的大小,一切都很好。但只要我改变这个多维数组中某些项的数据,我就会在关闭应用程序时收到AV。
下面是我的完整源代码:

unit Unit2;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls;

type
  TSubImage = record
    LeftBound: Integer;
    RightBound: Integer;
    TopBound: Integer;
    BottomBound: Integer;
  end;

  TForm2 = class(TForm)
    Button1: TButton;
    ListBox1: TListBox;
    Image1: TImage;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form2: TForm2;
  SubImages: Array of TSubImage;

implementation

{$R *.dfm}

procedure TForm2.Button1Click(Sender: TObject);
var X,Y,I: Integer;
    RegionMask: Array of Array of Integer;
begin
  SetLength(RegionMask,Image1.Width+1,Image1.Height+1);
  for Y := 0 to Image1.Height do
  begin
    for X := 0 to Image1.Width do
    begin
      if Image1.Canvas.Pixels[X,Y] <> clFuchsia then
      begin
        //Check left pixel
        if X > 0 then
        begin
          if RegionMask[X-1,Y] <> 0 then
          begin
            RegionMask[X,Y] := RegionMask[X-1,Y];
            //Check to se if pixel X position is leftwards to subimages left bound
            if Subimages[RegionMask[X,Y]].LeftBound > X then
              //Move subimage left bound to match pixel X position
              Subimages[RegionMask[X,Y]].LeftBound := X;
            //Check to se if pixel X position is rightwards to subimages right bound
            if Subimages[RegionMask[X,Y]].RightBound < X then
              //Move subimage right bound to match pixel X position
              Subimages[RegionMask[X,Y]].RightBound := X;
            //Check to se if pixel Y position is upwards to subimages top bound
            if Subimages[RegionMask[X,Y]].TopBound > Y then
              //Move subimage top bound to match pixel Y position
              Subimages[RegionMask[X,Y]].TopBound := Y;
            //Check to se if pixel Y position is downwards to subimages bottom bound
            if Subimages[RegionMask[X,Y]].BottomBound < Y then
              //Move subimage bottom bound to match pixel Y position
              Subimages[RegionMask[X,Y]].BottomBound := Y;
          end;
        end;
        //Check top pixel
        if Y > 0 then
        begin
          if RegionMask[X,Y-1] <> 0 then
          begin
            RegionMask[X,Y] := RegionMask[X,Y-1];
            //Check to se if pixel X position is leftwards to subimages left bound
            if Subimages[RegionMask[X,Y]].LeftBound > X then
              //Move subimage left bound to match pixel X position
              Subimages[RegionMask[X,Y]].LeftBound := X;
            //Check to se if pixel X position is rightwards to subimages right bound
            if Subimages[RegionMask[X,Y]].RightBound < X then
              //Move subimage right bound to match pixel X position
              Subimages[RegionMask[X,Y]].RightBound := X;
            //Check to se if pixel Y position is upwards to subimages top bound
            if Subimages[RegionMask[X,Y]].TopBound > Y then
              //Move subimage top bound to match pixel Y position
              Subimages[RegionMask[X,Y]].TopBound := Y;
            //Check to se if pixel Y position is downwards to subimages bottom bound
            if Subimages[RegionMask[X,Y]].BottomBound < Y then
              //Move subimage bottom bound to match pixel Y position
              Subimages[RegionMask[X,Y]].BottomBound := Y;
          end;
        end;
        //Create new region
        if RegionMask[X,Y] = 0 then
        begin
          SetLength(SubImages,Length(SubImages)+1);

          //If I comment out this line no exception is raised on closing the from
          RegionMask[X,Y] := Length(SubImages);

          //Set subimage initial bounds which are coordinates of one pixel
          //since we created new region for this pixel
          SubImages[RegionMask[X,Y]].LeftBound := X;
          SubImages[RegionMask[X,Y]].RightBound := X;
          SubImages[RegionMask[X,Y]].TopBound := Y;
          SubImages[RegionMask[X,Y]].BottomBound := Y;
        end;
      end;
    end;
  end;
  Form2.Caption := IntToStr(Length(SubImages)-1);
  for I := 0 to Length(Subimages)-1 do
  begin
    ListBox1.Items.Add(IntToStr(SubImages[I].LeftBound)+','+
                       IntToStr(SubImages[I].RightBound)+','+
                       IntToStr(SubImages[I].TopBound)+','+
                       IntToStr(SubImages[I].BottomBound));
  end;
  SetLength(RegionMask,0,0);
  RegionMask := nil;
end;

end.
mftmpeh8

mftmpeh81#

在这些行中出现一次性错误。请考虑第一次向SubImages添加元素时的情况。SubImages的长度为1,但数组中的唯一元素是SubImages[0]。您将RegionMask[X,Y]设置为1,然后使用该值对数组进行索引。因此,您正在尝试访问数组末尾以外的一个项。

SetLength(SubImages,Length(SubImages)+1);

      RegionMask[X,Y] := Length(SubImages);

      SubImages[RegionMask[X,Y]].LeftBound := X;
6l7fqoea

6l7fqoea2#

你知道一个好的方法来配置Delphi XE3或更新版本,使其总是为每个新项目启用范围检查吗?-SilverWarior
我有一个"默认"项目。所以,我从不启动一个新项目,而是复制那个项目。那个"默认"项目为我做了很多额外的事情,包括improved support for TApplication
你确实是对的,任何应用程序/项目都不应该在没有溢出检查、范围检查和Assert默认打开的情况下启动(对于调试版本)。
当然,还有更多的设置应该在默认情况下打开,以便进行正确的调试。

    • 溢出检查**

这将检查某些整数算术运算(+、-、*、Abs、Sqr、Succ、Pred、Inc和Dec)是否溢出。例如,在+(加法)运算之后,编译器将插入附加的二进制代码,以验证运算结果是否在支持的范围内。
当对整数变量的运算产生的结果超出该变量的范围时,就会发生"整数溢出"。例如,如果将整数变量声明为16位有符号整数,则其值的范围可以是-32768到32767。如果对该变量的运算产生的结果大于32767或小于-32768,发生整数溢出。
发生整数溢出时,操作的结果是未定义的,可能导致程序中出现未定义的行为:·Wrap-around结果可能会产生一个wrap-around值,这意味着数字32768实际上会存储为1,因为它比我们能存储的最大值高1个单位(32767)。·截断结果可能会被截断或修改,以适合整数类型的范围。例如,数字32768实际上将被存储为32767,因为这是我们可以存储的最高值。
未定义的程序行为是最严重的错误之一,因为它不是一个容易重现的错误,所以很难跟踪和修复。
如果您激活此选项,需要付出很小的代价:程序的速度将稍微降低。

    • IO检查**

检查I/O操作的结果。如果I/O操作失败,则会引发异常。如果关闭此开关,则必须手动检查I/O错误。如果激活此开关,则需要付出较小的代价:程序的速度将降低,但并不显著,因为与I/O操作本身所需的毫秒级时间相比,此检查引入的几微秒根本算不上什么(硬盘驱动器很慢)。

    • 范围检查**

Delphi极客称之为"最重要的Delphi设置",我完全同意。它检查所有数组和字符串索引表达式是否在定义的范围内。它还检查所有标量和子范围变量的赋值是否在范围内。
下面是一个示例代码,如果范围检查不可用,它会毁了我们的生活:

Type 
    Pasword= array [1..10] of byte; // we define an array of 10 elements
…
x:= Pasword[20];       // Range Checking will prevent the program from accessing element 20 (ERangecheckError exception is raised). Security breach avoided. Error log automatically sent to the programmer. Bruce Willis saves everyone.
    • 启用运行时错误检查**

要激活运行时错误检查,请转到"项目选项"并选中以下三个框:
在"项目选项"

中启用运行时错误检查

    • Assert**

一个好的程序员必须在代码中使用Assert来提高程序的质量和稳定性。说真的,伙计!你真的需要使用Assert。
Assert用于检查在程序的某个点上应该始终为真的条件,并在不满足条件时引发异常。在SysUtils单元中定义的Assert过程通常用于执行Assert。
你可以把Assert看作是穷人的单元测试。我强烈建议你深入研究Assert。它们非常有用,而且不需要像单元测试那样多的工作。
典型示例:

SysUtils.Assert(Input <> Nil, ‘The input should not be nil!’);

但是为了让程序检查我们的Assert,我们需要在项目设置-〉编译器选项中激活这个特性,否则它们将被忽略,就像它们不在我们的代码中一样。确保你理解我刚才所说的含义!例如,如果我们在Assert中调用有副作用的例程,我们会搞砸。在下面的例子中,在调试过程中,当Assert打开时,Test()函数将被执行,并且"This was executed"将出现在备忘录中。2然而,在发布过程中,该文本将不会出现在备忘录中,因为现在Assert被简单地忽略了。3恭喜,我们刚刚使程序在调试/发布模式下有了不同的行为。

function TMainForm.Test: Boolean;
begin
 Result:= FALSE;
 mmo.Lines.Add('This was executed');
end;

procedure TMainForm.Start;
VAR x: Integer;
begin
 x:= 0;
 if x= 0
 then Assert(Test(), 'nope');
end;

下面是一些如何使用它的示例:
1要检查输入参数是否在0..100范围内:

procedure DoSomething(value: Integer);
begin
  Assert((value >= 0) and (value <= 100), 'Value out of range');
  …
end;

2在使用指针之前检查它是否不为空:

Var p: Pointer;
Begin
  p := GetPointer;
  Assert(Assigned(p), 'Pointer is nil');
   …
End;

3在继续之前检查变量是否具有特定值:

var i: Integer;
begin
   i := GetValue;
   Assert(i = 42, 'Incorrect response to “What is the answer to life”!');
  …
end;

Assert也可以通过在项目选项中定义NDEBUG符号或使用{$D -}编译器指令来禁用。
在某些情况下,我们还可以使用Assert作为处理错误和异常的更优雅的方式,因为它更具可读性,而且它还包括一个自定义消息,这将帮助开发人员了解哪里出错了。

就我个人而言,我经常在例程的顶部使用它来检查参数是否有效。
激活这个特性会(自然地)使你的程序变慢,因为...嗯,有一行额外的代码要执行。
"没有什么是免费的"
一切美好的事物都是有代价的(幸运的是,我们的代价很小):启用运行时错误检查和Assert会降低程序的速度,并使程序变得更大。
现在的电脑有很多内存,所以内存的微小增加是无关紧要的,所以我们先把它放在一边,但让我们看看速度,因为这不是我们可以轻易忽视的:

Type                 Disabled   Enabled
Range checking       73ms         120ms
Overflow checking    580ms        680ms
I/O checking         Not tested   Not tested

正如我们所看到的,程序的速度受到这些运行时检查的强烈影响。如果我们有一个程序,速度是关键,我们最好只在调试期间激活“运行时错误检查”。我所做的,我也让它在第一个版本激活,并等待几个星期。如果没有错误报告,然后我发布了一个更新,其中“运行时错误检查”关闭。
就我个人而言,我让“IO检查”始终处于活动状态。由于这种检查而对性能的影响是微观的。

重要警告:

如果你有一个已经存在的项目写得不太好,并且你激活了下面的任何一个运行时错误检查,你的程序可能会比平常更频繁地崩溃。运行时错误检查例程并没有破坏你的程序。它总是被破坏--你只是不知道。运行时检查例程现在正在寻找所有那些代码可疑、低劣和发臭的地方。运行时检查的唯一目的是查找程序中的错误。

相关问题