Delphi compiler bug

Июнь 24, 2012 — Шарахов А.П.

Компилятор Delphi с целью оптимизации скорости выполнения программы порождает код, который может временно хранить в регистре процессора вычисленное значение элемента массива или поля записи для того, чтобы затем повторно использовать его. При этом значение той же переменной в оперативной памяти может измениться, минуя регистр, который продолжит хранить устаревшее значение. Проблема состоит в том, что компилятор Delphi иногда этого не замечает и генерирует код, использующий в вычислениях старое значение.

Вчера один посетитель форума столкнулся с проблемой, благодаря которой был обнаружен баг, который, похоже, присутствует во всех версиях Delphi. Неверный код получается при компиляции в режиме оптимизации следующих фрагментов:

//array optimization error
procedure TForm1.Button1Click(Sender: TObject);
var
  a: array[0..0] of integer;
  i, j: integer;
begin;
  for i:=0 to 0 do begin;
    a[i]:=0;
    a[0]:=1;
    j:=a[i];
    end;
  Memo1.Lines.Add(IntToStr(j)); // 0, must be 1
  end;

//record optimization error
procedure TForm1.Button2Click(Sender: TObject);
type
  PR= ^TR;
  TR= record
    i: integer;
    end;
var
  p: PR;
  r: TR;
  i: integer;
begin;
  p:=@r;
  p.i:=0;
  r.i:=1;
  i:=p.i;
  Memo1.Lines.Add(IntToStr(i)); // 0, must be 1
  end; 

В обоих случаях выводится 0 вместо 1.

Заблуждаетесь, если вам кажется, что примеры искусственны. Наоборот, подобные случаи довольно жизненны:

procedure TForm1.Button3Click(Sender: TObject);
var
  a: array[0..99] of integer;
  i, sum, j: integer;
begin;
  sum:=0;
  for i:=0 to 99 do begin;
    a[i]:=i;
    inc(a[i-i mod 10]);
    sum:=sum+a[i];
    end;
  Memo1.Lines.Add(IntToStr(sum)); // 4950
 
  sum:=0;
  for i:=0 to 99 do begin;
    a[i]:=i;
    inc(a[i-i mod 10]);
    j:=i;
    sum:=sum+a[j];
    end;
  Memo1.Lines.Add(IntToStr(sum)); // 4960
  end;

Можете долго ломать голову, размышляя над тем, почему вычисленные суммы не совпадают и почему явно “ненужная” строчка заставляет программу считать правильно. Пока не заглянете в окно CPU.

А вот головоломка для записей:

function SomeFunction1(const pp: PPoint): BOOL;
begin;
  Result:=true;
  end;
 
function SomeFunction2(const pp: PPoint): integer;
begin;
  Result:=0;
  end;
 
function SomeFunction3(Y: integer): integer;
begin;
  Result:=Y;
  end;
 
procedure TForm1.Button4Click(Sender: TObject);
var
  pp: PPoint;
  pn: TPoint;
  SaveY: integer;
  b: BOOL;
begin;
  pp:=@pn;
  b:=SomeFunction1(pp);
  pp.Y:=SomeFunction2(pp);
  if b then inc(pn.Y);
  SaveY:=SomeFunction3(pp.Y);
  Memo1.Lines.Add(IntToStr(SaveY)); // 0, must be 1
  end;  

Догадались, в чем дело?

на главную