我有以下在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;
如果我运行它,它会显示变量的地址。
变量i
和t
的内存范围为buf
!
当i
达到3时,作业buf[i].Value := 0;
会覆盖i
的前3个字节和t
的最后一个字节。这会导致无限循环,因为当i
到达0
时,3
会全部重置为SetLength(buf,20);
。
如果我用{{1}}自己分配内存,一切都很好。
图片显示我的意思。
我的设置:
感谢。
修改
这是相同的例子,但也许更好地理解我的意思:
和顺便说一句:抱歉我的英语不好;)
答案 0 :(得分:11)
这绝对看起来像编译错误。它只影响在堆栈上分配的TPair
数组。例如,这可以编译并运行良好:
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.
然而,这证明了错误:
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.
编译器似乎错误估算了TPair<Integer,Integer>
的大小。编译后的程序集显示序言如下:
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
编译器在堆栈上只分配了7个双字。第一个用于整数i
,仅为TPair
数组分配6个dword,这是不够的(SizeOf(TPair<integer,integer>)
等于8 - >两个dwords)。在第三次迭代中,mov [ebp+eax*8-$15],edx
(即:buf[2].Value
)进入i
的堆栈位置并将其值设置为零。
您可以通过在堆栈上强制足够的空间来演示工作程序:
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.
这是在XE2中测试过的,但如果您也看到问题,这似乎至少会持续到XE5。
答案 1 :(得分:5)
很明显,@ J ...将此识别为编译器错误是正确的。从我的测试中我发现它折磨了32位和64位Windows版本的编译器。我不知道OSX编译器或移动编译器。
有一些合理的解决方法可用。这个问题会产生合理的输出:
{$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.
同样这一个:
{$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.
或者确实如此:
{$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.
即便如此:
{$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.
而且:
{$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.
所以似乎只要编译器在遇到局部变量声明之前已经实例化了泛型类型,它就能够保留正确的堆栈大小。