【问题标题】:Weird class instance sizing using class members that are arrays of generic types使用泛型类型数组的类成员进行奇怪的类实例大小调整
【发布时间】:2011-12-17 12:05:14
【问题描述】:

下面是一段非常简单的代码,它模仿了我拥有的一些代码中的类结构(表单只包含一个附加到单击事件的按钮)。我正在使用 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.

【问题讨论】:

  • 我有点不知所措,但也许这些信息会有所帮助。大多数内置类都有 Clear 方法,当对象被销毁时会自动调用 Clear 方法。

标签: delphi generics delphi-xe delphi-xe2


【解决方案1】:

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

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,issue #100561

作为一种解决方法,我建议您使用在构造函数中使用 SetLength 分配的动态数组。我宁愿认为是固定大小的泛型数组的存在导致编译器行为不端。

【讨论】:

  • 我会举报的。对于 QC 报告,谁输入它真的很重要吗?此外,您似乎拥有最/更简洁的代码来重现它。
  • 嗨,大卫,感谢您的浏览。一定要为它创建一个 Quality Central 项目 :-) 你认为有办法解决这个问题吗?谢谢,雷蒙德。
  • 我对这行很感兴趣: TMyClassSingle.Create.V[256*256] := 0;我以前从未见过这种语法!
  • 这只是一种创建对象并在一行中写入字段的懒惰方式。而且我需要使数组足够大以击败内存管理器子分配策略。它自然会泄漏,但由于物体的大小不合适,我不承认您的荣誉!
  • 我擅长在一行中进行创建和字段访问等,它是以新的方式将单个值分配给数组!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-05-25
  • 1970-01-01
相关资源
最近更新 更多