使用泛型类型数组的类成员调整类实例大小

发布于 2024-12-12 16:04:24 字数 1737 浏览 0 评论 0原文

下面是一段非常简单的代码,它模仿了我拥有的一些代码中的类结构(该表单仅包含附加到单击事件的单个按钮)。我正在使用 Delphi XE 和 XE II,并且在销毁该类所基于的生产代码中的对象时看到严重的崩溃。仅当我允许在 Clear() 方法中初始化 TMyClass 中的数组项的成员时,才会发生这些崩溃。

不幸的是,到目前为止,在这个示例代码中,崩溃是不可重复的,但它确实模仿了一些非常奇怪的行为,我怀疑这可能是问题的原因。

如果我在 TMyClass.Clear 函数中放置一个断点,并查看该类的 InstanceSize 成员,则报告为 1288。这很奇怪,因为单独的数组成员的大小实际上是 12kb。我可以将 TRecord2 提供的类型更改为 Integer,并且得到相同的 InstanceSize 结果。如果我从类中完全删除数组,我会得到约 264 字节的实例大小,对于仅包含单个 128 字节记录实例的类来说,这似乎超出了上限。这表明编译器已为 TMyClass 中的 V(TT 类型)成员的数组存储分配了 1024 字节。

我怀疑当我将 12kb 数据写入编译器认为大小为 1kb 的对象时,奇怪的实例大小会导致发生不好的事情。

unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

type
  TRecord = record A : Array[1..128] of byte; end;
  TRecord2 = packed record S : Single; T : TDateTime; end;

  TBase = class(TObject)
    public
      R : TRecord;
  end;

  TBase2<T> = class(TBase)
    public
      type
        TT = packed array [0..31, 0..31] of T;

      var
        V : TT;
        R2 : TRecord;
  end;

  TMyClass = class(TBase2<TRecord2>)
    public
      procedure Clear;
  end;

procedure TForm1.Button1Click(Sender: TObject);
var
  O : TMyClass;
begin
  O := TMyClass.Create;
  O.Clear;
  O.Free;
end;

{ TMyClass }

procedure TMyClass.Clear;
var
  i, j : integer;
begin
  for i := 0 to 31 do
    for j := 0 to 31 do
      begin
        V[I, J].S := 0;
        V[I, J].T := 0;
      end;
end;

end.

Below is a very simple bit of code that mimics a class structure in some code I have (the form just contains a single button attached to the click event). I am using Delphi XE and XE II and see nasty crashes when destroying objects in my production code that this class is based on. These crashes only occur if I allow the members of the array item in TMyClass to be initialised in the Clear() method.

Unfortunately the crash is not repeatable so far in this sample code, but it does mimic some very weird behaviour with instance sizing that I suspect might be the cause of the problem.

If I place a break point in the TMyClass.Clear function and look at the InstanceSize member of the class reports 1288. Which is odd, as the size of the array member alone is really 12kb. I can change the type being provided from TRecord2 to Integer and I get the same InstanceSize result. If I remove the array entirely from the class I get an Instance size of ~264 bytes, which seems over the top for a class that contains only a single 128 byte record instance. This indicates that the compiler has allocated 1024 bytes to the array storage for the V (of type TT) member in TMyClass.

I am suspicious that the weird instance size is causing bad things to happen when I write 12kb of data into an object that the compiler thinks is 1kb in size.

unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

type
  TRecord = record A : Array[1..128] of byte; end;
  TRecord2 = packed record S : Single; T : TDateTime; end;

  TBase = class(TObject)
    public
      R : TRecord;
  end;

  TBase2<T> = class(TBase)
    public
      type
        TT = packed array [0..31, 0..31] of T;

      var
        V : TT;
        R2 : TRecord;
  end;

  TMyClass = class(TBase2<TRecord2>)
    public
      procedure Clear;
  end;

procedure TForm1.Button1Click(Sender: TObject);
var
  O : TMyClass;
begin
  O := TMyClass.Create;
  O.Clear;
  O.Free;
end;

{ TMyClass }

procedure TMyClass.Clear;
var
  i, j : integer;
begin
  for i := 0 to 31 do
    for j := 0 to 31 do
      begin
        V[I, J].S := 0;
        V[I, J].T := 0;
      end;
end;

end.

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

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

发布评论

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

评论(1

弥枳 2024-12-19 16:04:25

这是一个错误,正如这个最小的复制所示:

program InstanceSizeBug;

{$APPTYPE CONSOLE}

type
  TMyClass<T> = class
    V: array [0..255] of T;
  end;
  TMyClassSingle = class(TMyClass<Single>);

begin
  Writeln(TMyClass<Single>.InstanceSize);
  Writeln(TMyClassSingle.InstanceSize);
  Readln;
end.

输出:

1032
264

请注意,在 Delphi 2010 中可以观察到相同的行为,这是我必须掌握的唯一的其他 Delphi 版本。

在调试器下进行跟踪,您会发现自己处于 _GetMem 中,其中 Size 参数等于 264,因此显然没有分配足够的内存。

为了以防万一,这个版本因 AV 而失败。

program InstanceSizeBug;

{$APPTYPE CONSOLE}

type
  TMyClass<T> = class
    V: array [1..256*256] of T;
  end;
  TMyClassSingle = class(TMyClass<Single>);

begin
  TMyClassSingle.Create.V[256*256] := 0;
end.

我已将此问题提交给 Quality Central,问题为 #100561

作为解决方法,我建议您使用动态数组,并在构造函数中使用 SetLength 进行分配。我宁愿想象是固定大小的通用数组的存在导致编译器行为不当。

It's a bug, as is shown by this minimal reproduction:

program InstanceSizeBug;

{$APPTYPE CONSOLE}

type
  TMyClass<T> = class
    V: array [0..255] of T;
  end;
  TMyClassSingle = class(TMyClass<Single>);

begin
  Writeln(TMyClass<Single>.InstanceSize);
  Writeln(TMyClassSingle.InstanceSize);
  Readln;
end.

Output:

1032
264

Note that the same behaviour can be observed in Delphi 2010, the only other Delphi version i have to hand.

Trace through under the debugger and you find yourself in _GetMem with the Size parameter equal to 264 so clearly not enough memory is being allocated.

And just in case there is any doubt, this version fails with an AV.

program InstanceSizeBug;

{$APPTYPE CONSOLE}

type
  TMyClass<T> = class
    V: array [1..256*256] of T;
  end;
  TMyClassSingle = class(TMyClass<Single>);

begin
  TMyClassSingle.Create.V[256*256] := 0;
end.

I have submitted this to Quality Central, issue #100561.

As a workaround I suggest you use a dynamic array which you allocate using SetLength in the constructor. I rather imagine that it is the presence of a fixed sized generic array that is making the compiler misbehave.

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