Антон Григорьев дата публикации 24-07-2002 16:53 КАТЕГОРИЯ | | ПРОГРАММИСТ .Побочные эффекты при вычислении выражений с функциями | ПРОДУКТ | | Delphi | ПЛАТФОРМА | | |
function GetZero(Canvas:TCanvas):Integer;
begin
Result:=0;
Canvas.Font.Size:=8
end;
procedure TForm1.SomeProcedure;
var a,b:Integer;
begin
Canvas.Font.Name:='Times';
Canvas.Font.Size:=15;
a:=GetZero(Canvas)+Canvas.TextHeight('A');
Canvas.Font.Size:=15;
b:=Canvas.TextHeight('A')+GetZero(Canvas);
......
end;
Итак, функция GetZero возвращает 0, но при этом изменяет размер шрифта.
Она его действительно изменяет, это легко проверить. Какие значени
должны получить переменные a и b? Для a сначала вызывается GetZero, и
кегль шрифта устанавливается в 8, а потом вызывается TextHeight,
которая при восьмом кегле и 96 dpi возвращает 17. Для b же всё наоборот
- сначала, при кегле 15, вызывается TextHeight, которая вернёт 22, а
потом - GetZero. Добавление нуля ничего не меняет, а изменение кегля -
тем более, потому что TextHeight уже отработала. На самом деле всё
совсем не так. И a, и b получит значение 22, то есть в обоих случаях
сначала вызывается TextHeight, и только потом - GetZero. Это видно и из
ассемблерного листинга программы. Эффект проявляется и в Delphi 3.0, и
в Delphi 4.3, в остальных версиях я не проверял (проверьте, кто
сможет). Включение и выключение оптимизации ничего не меняет. Осталось
предположить, что в Inprise\Borland очень хорошо знают математику (от
перестановки слагаемых сумма не изменяется), но совсем забыли
программирование (процесс вычисления первого слагаемого может повлиять
на значение второго слагаемого). Чтобы переменная a получила всё-таки
нужное значение, приходится идти на хитрость и писать:
a:=GetZero(Canvas);
Inc(a,Canvas.TextHeight('A'));
Компилятор сам выбирает порядок вычисления выражения, в рамках правил
данного языка, стремясь построить оптимальный код. Он не может и не должен
учитывать зависимость одного слагаемого от другого. Налицо типичный
просчет программиста, предположившего выполнение нужного ему побочного
эффекта при определенном порядке вычислений операндов выражения.
Подробно о побочных эффектах функций можно почитать в литературе -
например, книга Роберта У. Себесты "Основные концепции языков
программирования", параграф 6.2.2. Ниже приведены отрывки из этой книги.
... Если ни один из операндов не имеет побочных эффектов, то порядок
вычисления несущественен.
... Побочный эффект (side effect) функции, называемый функциональным
побочным эффектом (functional side effect), возникает при изменении
функцией одного из своих параметров или глобальной переменной.
... Существуют два возможных решения проблемы, связанной с определением
порядка вычисления операндов. Во-первых, разработчики языка могут
запретить возможность воздействия функции на величину выражения, просто
не допуская функционального побочного эффекта. Второй метод борьбы с
этой проблемой - указать в определении языка точный порядок
вычисления операндов выражений и потребовать от разработчиков средств
реализации языка придерживаться именно этого порядка.
... Запретить функциональный побочный эффект трудно, и такой подход
уничтожает некоторую гибкость программирования.
... Проблема, связанная с наличием строгого порядка вычислений, состоит
в том, что некоторые используемые компиляторами методики оптимизации
программ содержат вычисление переупорядоченных операндов. Если же
строго задать порядок вычисления, то эти методы оптимизации, выполняемые
при вызовах функций, будут не доступны.
... Языки Pascal и Ada позволяют операндам бинарных выражений
вычисляться в любом порядке, выбираемом разработчиком средств
реализации языка. Более того, функции этих языков могут иметь побочные
эффекты, что порождает описанные ранее проблемы.
[Видимость переменных] [Процедуры и функции]
Обсуждение материала [ 24-11-2008 02:54 ] 12 сообщений |