Чтение онлайн

ЖАНРЫ

О чём не пишут в книгах по Delphi

Григорьев Антон Борисович

Шрифт:

Отметим одну ошибку, которую делают новички, прочитавшие комментарий про

ShareMem
, но не умеющие работать с
PChar
. Они пишут, например, такой код для функции, находящейся в DLL и возвращающей строку (листинг 3.46).

Листинг 3.46. Неправильный способ возврата строки из DLL

function SomeFunction(...): PChar;

var

 S: string;

begin

 // Здесь присваивается значение S

 Result := PChar(S);

end;

Такой код компилируется и даже, за редким исключением, дает

ожидаемый результат. Но тем не менее, в этом коде грубая ошибка. Указатель, возвращаемый функцией, указывает на область памяти, которая считается свободной, поскольку после выхода переменной
S
за пределы области видимости память, которую занимала эта строка, освободилась. Менеджер памяти может в любой момент вернуть эту память системе (тогда обращение к ней вызовет Access violation) или задействовать для других целей (тогда новая информация уничтожит содержащуюся там строку). Проблема маскируется тем, что обычно результат используется немедленно, до того как менеджер памяти что-то сделает с этим блоком. Тем не менее полагаться на это и писать такой код не следует.

3.4. Прочие "подводные камни"

В этом разделе собрана небольшая коллекция не связанных между собой "подводных камней", с которыми пришлось столкнуться автору книги.

3.4.1. Порядок вычисления операндов

Эта проблема связана с тем, что у человека есть определенные интуитивные представления о порядке выполнения действий программой, однако компилятор не всегда им соответствует. Рассмотрим следующий код (листинг 3.47, пример OpOrder на компакт-диске).

Листинг 3.47. "Неправильный" порядок вычисления операндов

var

 X: Integer;

function GetValueAndModifyX: Integer;

begin

 X := 1;

 Result := 2;

end;

procedure TForm1.Button1Click(Sender: TObject);

var

 A1, A2: Integer;

begin

 X := 2;

 A1 := X + GetValueAndModifyX;

 X := 2;

 А2 := GetValueAndModifyX + X;

 Label1.Caption := IntToStr(A1);

 Label2.Caption := IntToStr(A2);

end;

Суть этого примера заключается в том, что функция

GetValueAndModifyX
имеет побочный эффект — изменяет значение глобальной переменной
X
. И эту же переменную мы используем при вычислении выражения, в которое входит также вызов
GetValueAndModifyX
. При вычислении
A1
в выражении сначала упоминается
X
, а потом
GetValueAndModifyX
, при вычислении
А2
— наоборот. Логично было бы предположить, что
A1
получит значение 4,
А2
— 3, т.к. вычисление первого операнда должно выполняться раньше второго. В действительности же обе переменные получат значение 3, поскольку компилятор сам выбирает порядок вычисления операндов независимо от того, в каком порядке они упоминаются в выражении. То же самое касается любых коммутативных операций: умножения, арифметических
and
,
or
и
xor
. Посмотрим, что будет для некоммутативных операций, например, для деления (листинг 3.48).

Листинг 3.48. "Неправильный" порядок вычисления операндов при делении

procedure TForm1.Button2Click(Sender: TObject);

var

 A1, A2: Extended;

begin

 X := 2;

 A1 := X / GetValueAndModifyX;

 X := 2;

 A2 := GetValueAndModifyX / X;

 Label1.Caption := FloatToStr(A1);

 Label2.Caption := FloatToStr(A2);

end;

В

результате выполнения этого кода
A1
получает значение 0.5,
A2
— 2, т.е. и здесь сначала вычисляется функция, а потом берется значение переменной
X
.

Если бы функция

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

Примечание

Побочные эффекты в функциях настолько небезопасны, что в некоторых языках они полностью запрещены. Например, в Аде изменять значения глобальных переменных могут только процедуры, но не функции.

Ради интереса посмотрим, что будет, если вторым аргументом тоже будет функция, зависящая от

X
, (листинг 3.49).

Листинг 3.49. Сложение двух операндов с побочными эффектами

function GetX: Integer;

begin

 Result := X;

end;

procedure TForm1.Button3Click(Sender: TObject);

var

 A1, A2: Integer;

begin

 X:= 2;

 A1 := GetX + GetValueAndModifyX;

 X := 2;

 A2 := GetValueAndModifyX + GetX;

 Label1.Caption := IntToStr(A1);

 Label2.Caption := IntToStr(A2);

end;

Здесь

A1
получит значение 4,
A2
— 3, т.e. интуитивно ожидаемые. Тем не менее полагаться на интуицию все же не стоит: в более сложных случаях она может подвести. Дело в том, что стандарт языка Паскаль разрешает разработчикам конкретной реализации языка самим выбирать порядок вычисления операндов [5]. Поэтому, даже если вам удалось добиться желаемого порядка вычисления, в следующих версиях Delphi (или при переносе на другую платформу) программа может начать работать неправильно. Таким образом, разработчик не имеет права делать какие-то предположения о том, в каком порядке будут вычисляться операнды, а когда изменение этого порядка может повлиять на результат, код должен быть написан таким образом, чтобы исключить эту возможность. В частности, пример со сложением должен быть переписан так (листинг 3.50).

Листинг 3.50. Явное управление порядком вычисления операндов

procedure TForm1.Button1Click(Sender: TObject);

var

 A1, A2: Integer;

begin

 X := 2;

 A1 := X;

 Inc(A1, GetValueAndModifyX);

 X := 2;

 A2 := GetValueAndModifyX;

 Inc(A2, X);

 Label1.Caption := IntToStr(A1);

Поделиться с друзьями: