i have following code example compiled in delphi xe5 update 2.
procedure TForm1.FormCreate(Sender: TObject);
var i,t:Integer;
buf: array [0..20] of TPair<Integer,Integer>;
begin
t := 0;
for i := Low(buf) to High(buf) do begin
ShowMessage(
Format(
'Pointer to i = %p;'#$d#$a+
'Pointer to buf[%d].Key = %p;'#$d#$a+
'Pointer to buf[%d].Value = %p;'#$d#$a+
'Pointer to t = %p',
[@i, i, @(buf[i].Key), i, @(buf[i].Value), @t]
)
);
buf[i].Key := 0;
buf[i].Value := 0;
t := t + 1;
end;
end;
if i run it it shows me the adresses of the variables.
the variables i
and t
have adresses in the memory range of buf
!
when i
reaches 3 the assignment buf[i].Value := 0;
overwrite first 3 bytes of i
and the last byte of t
. this results in a infinity loop because i
gets allways reset to 0
when it reaches 3
.
if i allocate the memory by myself with SetLength(buf,20);
everything is fine.
the picture shows what i mean.
my setup:
strange, isn't it?
can anybody reproduce it?
is it a bug in the delphi compiler?
thanks.
EDIT:
here is the same example, but maybe better to understand what i mean:
and btw.: sorry for my bad english ;)
This definitely looks like a compiler bug. It only affects an array of TPair
allocated on the stack. For example, this compiles and runs fine :
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
Generics.Collections;
var i:Integer;
buf: array [0..20] of TPair<Integer,Integer>;
begin
for i := Low(buf) to High(buf) do begin
buf[i].Key := 0;
buf[i].Value := 0;
end;
end.
This, however, demonstrates the error :
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
Generics.Collections;
procedure DoSomething;
var i:Integer;
buf: array [0..20] of TPair<Integer,Integer>;
begin
for i := Low(buf) to High(buf) do begin
buf[i].Key := 0;
buf[i].Value := 0;
end;
end;
begin
DoSomething;
end.
The compiler seems to be miscalculating the size of a TPair<Integer,Integer>
. The compiled assembly shows the preamble as follows :
Project1.dpr.14: begin
00445C50 55 push ebp
00445C51 8BEC mov ebp,esp
00445C53 83C4E4 add esp,-$1c //*** Allocate only 28 bytes (7words)
Project1.dpr.15: for i := Low(buf) to High(buf) do begin
00445C56 33C0 xor eax,eax
00445C58 8945FC mov [ebp-$04],eax
Project1.dpr.16: buf[i].Key := 0;
00445C5B 8B45FC mov eax,[ebp-$04]
00445C5E 33D2 xor edx,edx
00445C60 8954C5E7 mov [ebp+eax*8-$19],edx
Project1.dpr.17: buf[i].Value := 0;
00445C64 8B45FC mov eax,[ebp-$04]
00445C67 33D2 xor edx,edx
00445C69 8954C5EB mov [ebp+eax*8-$15],edx
Project1.dpr.18: end;
00445C6D FF45FC inc dword ptr [ebp-$04]
Project1.dpr.15: for i := Low(buf) to High(buf) do begin
00445C70 837DFC15 cmp dword ptr [ebp-$04],$15
00445C74 75E5 jnz $00445c5b
Project1.dpr.19: end;
00445C76 8BE5 mov esp,ebp
00445C78 5D pop ebp
00445C79 C3 ret
00445C7A 8BC0 mov eax,eax
The compiler has only allocated 7 dwords on the stack. The first is for the integer i
, leaving only 6 dwords allocated for the TPair
array, which is insufficient (SizeOf(TPair<integer,integer>)
is equal to 8 -> two dwords). On the third iteration, mov [ebp+eax*8-$15],edx
(ie: buf[2].Value
) runs into the stack location for i
and sets its value to zero.
You can demonstrate a working program by forcing enough space on the stack :
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
Generics.Collections;
procedure DoSomething;
var i:Integer;
fixalloc : array[0..36] of Integer; // dummy variable
// allocating enough space for
// TPair array
buf: array [0..20] of TPair<Integer,Integer>;
begin
for i := Low(buf) to High(buf) do begin
buf[i].Key := i;
buf[i].Value := i;
end;
end;
begin
DoSomething;
end.
This is tested in XE2, but it seems this persists through at least XE5 if you are also seeing the problem.
It is clear that @J... is correct in identifying this as a compiler bug. From my tests I observe that it afflicts both 32 and 64 bit Windows versions of the compiler. I don't know about the OSX compiler, or the mobile compilers.
There are some reasonable workarounds available. This problem produces reasonable output:
{$APPTYPE CONSOLE}
uses
System.SysUtils, Generics.Collections;
type
TFixedLengthPairArray = array [0..20] of TPair<Integer,Integer>;
procedure DoSomething;
var
i: Integer;
buf: TFixedLengthPairArray;
begin
Writeln(Format('%p %p', [@i, @buf]));
end;
begin
DoSomething;
end.
Likewise this one:
{$APPTYPE CONSOLE}
uses
System.SysUtils, Generics.Collections;
type
TFixedLengthPairArray = array [0..20] of TPair<Integer,Integer>;
procedure DoSomething;
var
i: Integer;
buf: array [0..20] of TPair<Integer,Integer>;
begin
Writeln(Format('%p %p', [@i, @buf]));
end;
begin
DoSomething;
end.
Or indeed this:
{$APPTYPE CONSOLE}
uses
System.SysUtils, Generics.Collections;
type
TPairOfIntegers = TPair<Integer,Integer>;
procedure DoSomething;
var
i: Integer;
buf: array [0..20] of TPairOfIntegers;
begin
Writeln(Format('%p %p', [@i, @buf]));
end;
begin
DoSomething;
end.
And even this:
{$APPTYPE CONSOLE}
uses
System.SysUtils, Generics.Collections;
type
TPairOfIntegers = TPair<Integer,Integer>;
procedure DoSomething;
var
i: Integer;
buf: array [0..20] of TPair<Integer,Integer>;
begin
Writeln(Format('%p %p', [@i, @buf]));
end;
begin
DoSomething;
end.
And this:
{$APPTYPE CONSOLE}
uses
System.SysUtils, Generics.Collections;
procedure DoSomething;
type
TPairOfIntegers = TPair<Integer,Integer>;
var
i: Integer;
buf: array [0..20] of TPair<Integer,Integer>;
begin
Writeln(Format('%p %p', [@i, @buf]));
end;
begin
DoSomething;
end.
So it seems that so long as the compiler has already instantiated the generic type before it encounters the local variable declaration, it is able to reserve the correct stack size.
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