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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With