delphi-локальная переменная и массив TPair-странное поведение выделения памяти
у меня есть следующий пример кода компилируется в Делфи xe5 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;
перезаписать первые 3 байта i
и последний байт t
. это приводит к бесконечности цикла, потому что i
всегда сбрасывается в 0
когда он достигает 3
.
если я выделяю память сама по себе с SetLength(buf,20);
все нормально.
картинка показывает, что я имею в виду.
Мои настройки:
- Windows 7 64 Бит
- Delphi XE 5 Update 2
- Отладка Конфигурации 32 Бит
странно, не правда ли?
может ли кто-нибудь воспроизвести его?
это ошибка в delphi компилятор?
спасибо.
EDIT:
вот тот же пример, но, возможно, лучше понять, что я имею в виду:
и кстати.: извините за мой плохой английский ;)
2 ответов
это определенно похоже на ошибку компилятора. Это влияет только на массив 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,-c //*** 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-],eax
Project1.dpr.16: buf[i].Key := 0;
00445C5B 8B45FC mov eax,[ebp-]
00445C5E 33D2 xor edx,edx
00445C60 8954C5E7 mov [ebp+eax*8-],edx
Project1.dpr.17: buf[i].Value := 0;
00445C64 8B45FC mov eax,[ebp-]
00445C67 33D2 xor edx,edx
00445C69 8954C5EB mov [ebp+eax*8-],edx
Project1.dpr.18: end;
00445C6D FF45FC inc dword ptr [ebp-]
Project1.dpr.15: for i := Low(buf) to High(buf) do begin
00445C70 837DFC15 cmp dword ptr [ebp-],
00445C74 75E5 jnz 445c5b
Project1.dpr.19: end;
00445C76 8BE5 mov esp,ebp
00445C78 5D pop ebp
00445C79 C3 ret
00445C7A 8BC0 mov eax,eax
компилятор выделил только 7 слов в стеке. Первый - для целого числа i
, оставив только 6 слов, выделенных для TPair
массив, которого недостаточно (SizeOf(TPair<integer,integer>)
равно 8 - > два слова). На третьей итерации, mov [ebp+eax*8-],edx
(ie: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, если вы также видите проблему.
ясно, что @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.
таким образом, кажется, что пока компилятор уже создал экземпляр универсального типа, прежде чем он столкнется с объявлением локальной переменной, он может зарезервировать правильный размер стека.