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); все нормально.

картинка показывает, что я имею в виду.

Output, Memory Adresses

Мои настройки:

  • Windows 7 64 Бит
  • Delphi XE 5 Update 2
  • Отладка Конфигурации 32 Бит

странно, не правда ли?
может ли кто-нибудь воспроизвести его?
это ошибка в delphi компилятор?

спасибо.

EDIT:
вот тот же пример, но, возможно, лучше понять, что я имею в виду: memory areas

и кстати.: извините за мой плохой английский ;)

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.

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