Такой код компилируется и даже, за редким исключением, дает ожидаемый результат. Но тем не менее, в этом коде грубая ошибка. Указатель, возвращаемый функцией, указывает на область памяти, которая считается свободной, поскольку после выхода переменной S
за пределы области видимости память, которую занимала эта строка, освободилась. Менеджер памяти может в любой момент вернуть эту память системе (тогда обращение к ней вызовет Access violation) или задействовать для других целей (тогда новая информация уничтожит содержащуюся там строку). Проблема маскируется тем, что обычно результат используется немедленно, до того как менеджер памяти что-то сделает с этим блоком. Тем не менее полагаться на это и писать такой код не следует.
3.4. Прочие "подводные камни"
В этом разделе собрана небольшая коллекция не связанных между собой "подводных камней", с которыми пришлось столкнуться автору книги.
3.4.1. Порядок вычисления операндов
Эта проблема связана с тем, что у человека есть определенные интуитивные представления о порядке выполнения действий программой, однако компилятор не всегда им соответствует. Рассмотрим следующий код (листинг 3.47, пример OpOrder на компакт-диске).
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).
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).
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;