Семь чудес и два фокуса на Дельфи (5136-1)

Посмотреть архив целиком

Семь чудес и два фокуса на Дельфи

Максим Кузьминский

Верите ли Вы в чудеса или нет, Вы наверняка согласитесь со мной, что иногда что-то такое случается с кодом наших программ, и они вдруг перестают компилироваться или, что еще коварнее, начинают выдавать совершенно непредсказуемый результат. И вот тогда, сознайтесь, вас начинают посещать странные мысли об участии во всех этих чудесах неких потусторонних сил.

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

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

Для того, что бы вы поняли, что я имею в виду, давайте рассмотрим один очень простой пример.

Чудо Первое (Round Miracle).

Откройте Delphi, создайте новый проект, назовите его AllMiracles, положите кнопку на главную форму и напишите в обработчике события OnClick следующий код:

procedure TfrmAllMiracles.btnRoundMrclClick(Sender: TObject);

begin

ShowMessage( IntToStr( Round(3.5) - Round(2.5) ) );

end;

Figure 1.

А теперь остановитесь и скажите, какой результат вы ожидаете увидеть. Я надеюсь вы не сказали "1", ведь иначе это не было бы чудо. Те, у кого хорошо развита интуиция, могут сказать "0", и это будет еще дальше от правильного ответа. И только те, кто часто играет в Спортлото или, на худой конец, внимательно читает документацию, ответит "2" и это будет правильно. Не верите? - жмите F9.

Читаем Help по функции Round:

Round returns an Int64 value that is the value of X rounded to the nearest whole number. If X is exactly halfway between two whole numbers, the result is always the even number.

Вот такое оно, "Круглое чудо".

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

Чудо Второе (Absolute Miracle).

Положите на главную форму созданного ранее проекта новую кнопку и напишите в его обработчике события OnClick такой код:

procedure TfrmAllMiracles.btnAbsMrclClick (Sender: TObject);

var

i1: int64;

begin

i1:= abs(low(integer));

ShowMessage(IntToStr(i1));

end;

Figure 2.

Прежде чем нажать F9, проанализируем написаное. Low от integer - значение известное всем, записанное даже в Help'е и равное -2147483648, т.е. число отрицательное.

Help не говорит о функции Abs ничего нового:

Abs returns the absolute value of the argument X. X is an integer-type or real-type expression.

Переменная i1 описана как int64, и это правильно, потому что 2147483648 - уже выходит за границы типа integer. Это значение (2147483648) мы и ожидаем увидеть на экране, не так ли? А вот и нет. Проверьте. На экране вновь - 2147483648. Как абсолютное значение может быть отрицательным?

Давайте еще раз, повнимательнее рассмотрим выражение abs(low(integer)). Что можно еще сказать про него? Не смотря на наличее в нем функций, это - константа

Читаем Help по теме "Constant expressions":

...Constant expressions cannot include variables, pointers, or function calls, except calls to the following predefined functions: Abs...Low...

попробуем описать константу со значением равным этому выражению:

...

const

ci = abs(low(integer));

...

Figure 3.

Код компилируется. Значит мы - правы, а это значит, что результат выражения определяется еще на стадии компиляции. Далее, low(integer)) имеет целый тип. Abs от integer - тоже целое, а нам нужно int64. Поробуем переписать код следующим образом:

procedure TfrmAllMiracles.btnAbsMrclClick (Sender: TObject);

const

ci = abs(low(integer));

var

i1: int64;

begin

// i1:= abs((low(integer)));

i1:= abs(int64(low(integer)));

ShowMessage(IntToStr(i1));

end;

Figure 4.

Теперь - заработало. Секрет "Абсолютного чуда" раскрыт! Кстати, abs(int64(low(integer))) - тоже константа.

Следующее чудо - пример того, как вполне правильный код отказывается компилироваться.

Чудо третье (One more low integer miracle).

Новая кнопка на форме будет реагировать на нажатие следующим образом:

procedure TfrmAllMiracles.btnLowIntMrclClick( Sender: TObject);

var

lowInt: integer;

begin

lowInt := -2147483648;

ShowMessageFmt('%d',[lowInt]);

end;

Figure 4.

Совершенно обычная процедура. У нас возникло желание присвоить некоторой переменной вполне законное значение. Но этот код не компилируется:

Overflow in conversion or arithmetic operation

Жмем F1 на сообщении об ошибке и читаем:

The compiler has detected an overflow in an arithmetic expression: the result of the expression is too large to be represented in 32 bits.

Видимо компилятор пытается определить константу целого типа со значением 2147483648, а только затем изменить ее знак, но это ему не удается. Перепишем код:

procedure TfrmAllMiracles.btnLowIntMrclClick( Sender: TObject);

var

lowInt: integer;

begin

lowInt := -int64(2147483648);

// lowInt := -2147483648;

ShowMessageFmt('%d',[lowInt]);

end;

Figure 5.

Вот теперь - все нормально. Пример очень незамысловат, но дает нам представление о том, как компилятор Delphi обрабатывает константы и определяет их тип.

А вот следующее чудо - пример того, к какой путанице может привести перегрузка функций. Такие чудеса мы зачастую сами устраиваем себе по невнимательности, а потом часами ищем ошибки.

Чудо четвертое (String Trick).

Ну, что ж, добавим опять кнопку на нашу форму и зададим следующий код для события OnClick:

procedure TfrmAllMiracles.btnCopyMrclClick (Sender: TObject);

const

cs: array[0..1] of char='01';

begin

ShowMessage(copy(cs,0,1)+copy(cs,1,1));

end;

Figure 6.

Я знаю, что вы уже ждете подвоха и все же результат может оказаться неожиданным: "00".

Как обычно обратимся к Help'у, смотрим функцию Copy:

Returns a substring of a string or a segment of a dynamic array.

...

function Copy(S; Index, Count: Integer): string;

function Copy(S; Index, Count: Integer): array;

...

Дело в том, что в выражении copy(cs,0,1)+copy(cs,1,1) оба раза вызываются разные версии функции copy, первый раз - для динамических массивов, которые нумеруются с 0, а второй раз - для строчек, первый элемент которых имеет индекс 1. Оба раза cs преобразуется к необходимому типу, и то, что cs, как массив начинается с нулевого элемента, в данном случае не имеет никакого значения.

А теперь, наконец, мы добрались и до обьектов. Множество Дельфийских чудес связаны с тем, что обьекты в Delphi - автоматически разыменуемые ссылки, которые могут указывать на освобожденную или занятую кем-то другим область памяти. О таких случаях написано немало. Наше чудо - иное.

Чудо пятое (Is-Miracle).

Опишите в разделе protected нашей формы поле FControl типа TСontrol и задайте для еще одной - новой кнопки такую вот реакцию на ее нажатие:

procedure TfrmAllMiracles.btnIsMrclClick(Sender: TObject);

begin

if (FControl is TControl) then

begin

if not Assigned(FControl) then

FControl := TControl.Create(Self);

end

else

ShowMessage('Not a Control');

end;

Figure 7.

Такое "Чудо" я видел несколько раз и в разных проявлениях. Сколько раз бы вы не нажимали на кнопку btnIsMrcl, вы каждый раз будете видеть сообщение 'Not a Control', а конструктор TControl так никогда и не будет вызван.

Вот, что говорит Help:

The expression object is class returns True if object is an instance of the class denoted by class or one of its descendants, and False otherwise. (If object is nil, the result is False.)

Дело в том, что оператор is использует ссылку на класс обьекта, а не то, как описана переменная, которая по сути - простой указатель. Так что TControl не всегда TControl.

Да, я надеюсь вы понимаете, что TControl здесь выбран случайно, с таким же успехом это мог быть и любой другой класс.

Случай когда FControl ссылается на уже освобожденный обьект или является локальной и непроинициализированной переменной, дает непредказуемые результаты и может привести к совсем не чудесному краху аппликации.

А вот для следующего чуда я нашел только косвенное обьяснение в Help'е и поэтому мы будем вынуждены провести небольшой эксперимент.

Чудо шестое (Is-Miracle II)

Давайте посмотрим еще на одно, похожее чудо связанное с оператором is. Добавим к нашей группе проектов (ProjectGroup1) новый проект - DLL с именем AllMirrLib, в единственном модуле которого будет следующий код:

library AllMirrLib;

uses

Controls;

function IsControlLib(const anObj: TObject): boolean;

begin

Result := anObj is TControl;

end;

exports

IsControlLib;

Figure 9.

Как вы видите эта библиотека экспортирует только одну очень простую функцию, которая возвращает знечение True в том случае, если ее единственный параметр происходит от TControl и False - в остальных случаях.

В модуль формы нашего основного проекта добавим следующее определение:

unit AllMir;

interface

...

implementation

{$R *.DFM}

function IsControlLib(const anObj: TObject): boolean; external 'AllMirrLib.DLL';

Figure 10.

Теперь, как обычно, добавим на форму новую кнопку:

procedure TfrmAllMiracles.btnIsMrcl2Click(Sender: TObject);


Случайные файлы

Файл
24720-1.rtf
151310.rtf
ORELP~1.DOC
71300.rtf
84053.rtf




Чтобы не видеть здесь видео-рекламу достаточно стать зарегистрированным пользователем.
Чтобы не видеть никакую рекламу на сайте, нужно стать VIP-пользователем.
Это можно сделать совершенно бесплатно. Читайте подробности тут.