Компилятор 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;
Догадались, в чем дело?