Странный размер экземпляра класса с использованием членов класса, которые являются массивами универсальных типов - PullRequest
8 голосов
/ 28 октября 2011

Ниже приведен очень простой фрагмент кода, который имитирует структуру класса в моем коде (форма содержит только одну кнопку, прикрепленную к событию click). Я использую Delphi XE и XE II и вижу неприятные сбои при уничтожении объектов в моем производственном коде, на котором основан этот класс. Эти сбои происходят только в том случае, если я разрешу инициализировать элементы элемента массива в TMyClass в методе Clear ().

К сожалению, до сих пор сбой не повторяется в этом примере кода, но он имитирует очень странное поведение с размером экземпляров, которое, как я подозреваю, может быть причиной проблемы.

Если я помещу точку останова в функцию TMyClass.Clear и посмотрю на члена InstanceSize отчетов о классе 1288. Это странно, поскольку размер одного члена массива действительно составляет 12 КБ. Я могу изменить предоставляемый тип с TRecord2 на Integer, и я получаю тот же результат InstanceSize. Если я полностью удаляю массив из класса, я получаю размер экземпляра ~ 264 байта, что кажется чрезмерным для класса, который содержит только один экземпляр записи размером 128 байт. Это указывает на то, что компилятор выделил 1024 байта для хранения массива для члена V (типа TT) в TMyClass.

Я подозреваю, что странный размер экземпляра вызывает плохие вещи, когда я записываю 12 КБ данных в объект, размер которого, по мнению компилятора, составляет 1 КБ.

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.

1 Ответ

8 голосов
/ 28 октября 2011

Это ошибка, как показывает это минимальное воспроизведение:

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 в конструкторе. Я скорее представляю, что именно наличие универсального массива фиксированного размера приводит к неправильной работе компилятора.

...