А. Григорьев - О чём не пишут в книгах по Delphi
- Название:О чём не пишут в книгах по Delphi
- Автор:
- Жанр:
- Издательство:БХВ-Петербург
- Год:2008
- Город:СПб
- ISBN:978-5-9775-019003
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
А. Григорьев - О чём не пишут в книгах по Delphi краткое содержание
Рассмотрены малоосвещённые вопросы программирования в Delphi. Описаны методы интеграции VCL и API. Показаны внутренние механизмы VCL и приведены примеры вмешательства в эти механизмы. Рассмотрено использование сокетов в Delphi: различные механизмы их работы, особенности для протоколов TCP и UDP и др. Большое внимание уделено разбору ситуаций возникновения ошибок и получения неверных результатов в "простом и правильном" коде. Отдельно рассмотрены особенности работы с целыми, вещественными и строковыми типами данных, а также приведены примеры неверных результатов, связанных с ошибками компилятора, VCL и др. Для каждой из таких ситуаций предложены методы решения проблемы. Подробно рассмотрен синтаксический анализ в Delphi на примере арифметических выражений. Многочисленные примеры составлены с учётом различных версий: от Delphi 3 до Delphi 2007. Прилагаемый компакт-диск содержит примеры из книги.
Для программистов
О чём не пишут в книгах по Delphi - читать онлайн бесплатно ознакомительный отрывок
Интервал:
Закладка:
В четвертом случае литерал также хранится в сегменте кода, но работы с указателем уже нет. Этот литерал занимает там пять байтов: один байт на длину и четыре — на символы. Переменная S
размешается в стеке, занимая там 256 байтов, а присваивание ей литерала — это копирование значения литерала из сегмента кода в область памяти, занятую переменной. Таким образом, в дальнейшем мы работаем не с константой в сегменте кода, а с ее копией в стеке, которую можно без проблем модифицировать.
В пятом случае мы получаем указатель на этот участок стека. Обратите внимание, что приведение типов в данном случае не работает: для записи в P
адреса первого символа строки приходится использовать оператор получения адреса @
. Модификация строки проходит, как и в предыдущем случае, успешно, но при присваивании выражения типа PChar
свойству типа AnsiString
длина строки определяется по правилам, принятым для PChar
, т.е. строка сканируется до обнаружения нулевого символа. Но поскольку
ShortString "не отвечает" за то, что будет содержаться в неиспользуемых символах, там может остаться всякий мусор от предыдущего использования стека. Никакой гарантии, что сразу после последнего символа будет #0
, нет. Отсюда и появление непонятных символов на экране.
Общий вывод таков: пока мы не вмешиваемся в работу компилятора с типами ShortString
и AnsiString
, получаем ожидаемый результат. Работа с этими же строками через PChar
в обход стандартных механизмов приводит к появлению проблем. Кроме того, при работе со строками PChar
необходимо четко представлять, где и как выделяется для них память, иначе можно получить неожиданную ошибку.
3.3.3. Приведение литералов к типу PChar
В разд. 1.1.13 мы уже говорили, что когда у функции есть параметр типа PChar
, и этот параметр не будет изменяться функцией, при вызове ей можно передавать строковый литерал (см. листинг 1.20). Компилятор размещает литерал в сегменте кода и передает функции указатель на эту память.
В примерах кода, приведенных на различных сайтах, можно нередко встретить такую ситуацию, когда литерал, передаваемый в качестве параметра типа PChar
, явно приводится к этому типу. Разберемся, что это дает. Для этого положим на форму четыре кнопки и напишем в обработчиках их нажатия следующий код (листинг 3.18. пример PCharLit
на компакт-диске).
PChar
procedure TForm1.Button1Click(Sender: TObject);
begin
Application.MessageBox('Text', nil, 0);
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
Application.MessageBox('A', nil, 0);
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
Application.MessageBox(PChar('Text'), nil, 0);
end;
procedure TForm1.Button4Click(Sender: TObject);
begin
Application.MessageBox(PChar('A'), nil, 0);
end;
Метод TApplication.MessageBox
по каким-то непонятным причинам имеет параметры типа PChar
вместо string
, и мы этим воспользуемся. При его вызове будет показано диалоговое окно с текстом, переданным в качестве первого параметра (в заголовке будет написано Ошибка, т.к. второй параметр у нас nil
). Нажатие на первую и вторую кнопку не приводит ни к каким неожиданностям — мы видим на экране Textи Асоответственно. Теперь перейдем к коду с явным приведением литерала к PChar
. Нажатие на третью кнопку к сюрпризам не приведет, а вот нажатие на четвертую даст исключение Access violation.
Происходит это потому, что тип литерала зависит не только от его вида, но и оттого, в каком контексте он упомянут. Например, в предыдущем разделе мы видели, что литерал 'Xest'
мог иметь тип string
или PChar
в зависимости от того, какой переменной он присваивался. Там, где явного приведения типов нет, тип литерала однозначно определяется по типу формального параметра, и в обработчиках нажатия первых двух кнопок компилятор создает правильные литералы 'Text'
и 'А'
типа PChar
. Явное приведение литерала к типу PChar
меняет контекст, в котором литерал упомянут, и компилятор может сделать неправильный вывод о его типе. В обработчике третьей кнопки компилятор правильно понимает, что литерал имеет тип PChar
и генерирует код, полностью эквивалентный коду обработчика первой кнопки. А вот в случае приведения к типу PChar
литерала 'А'
компилятор принимает этот литерал не за строковый, а за символьный (т.е. за литерал типа Char
), состоящий из одного символа без всяких добавлений длины, символа #0
и т.п. При приведении выражения типа Char
к любому указателю (в том числе и к PChar
) оно рассматривается как выражение любого порядкового типа, и его численное значение становится численным значением указателя. В нашем случае это символ с кодом 65 ($41 в шестнадцатиричной записи), поэтому в функцию передается указатель $00000041. Такой указатель указывает на ту область виртуальной памяти, которая никогда не отображается на физическую память, поэтому его использование приводит к ошибке Access violation.
Итак, мы увидели, что явное приведение литерала к типу PChar
либо никак не отражается на генерируемом компилятором коде (в случае литералов из нескольких символов), либо приводит к генерированию заведомо некорректного кода (в случае односимвольных литералов). Если еще учесть, что приведение литералов к PChar
загромождает код, легко сделать вывод, что приводить литералы к PChar
не нужно, поскольку это потенциальный источник проблем и признак плохого оформления кода.
3.3.4. Сравнение строк
Для типов PChar
и AnsiString
, которые являются указателями, понятие равенства двух строк может толковаться двояко: либо как равенство указателей, либо как равенство содержимого памяти, на которую эти указатели указывают. Второй вариант предпочтительнее, т.к. он ближе к интуитивному понятию равенства строк. Для типа AnsiString
реализован именно этот вариант, т.е. сравнивать такие строки можно, ни о чем не задумываясь. Более сложные ситуации мы проиллюстрируем примером Companions. В нем одиннадцать кнопок, и обработчик каждой из них иллюстрирует одну из возможных ситуаций.
Начнем со сравнения двух строк типа PChar
(листинг. 3.19).
PChar
procedure TForm1.Button1Click(Sender: TObject);
var
P1, P2: PChar;
begin
P1 := StrNew('Test');
P2 := StrNew('Test');
if P1 = P2 then Label1.Caption := 'Равно';
else Label1.Caption := 'Не равно';
StrDispose(P1);
StrDispose(P2);
end;
В данном примере мы увидим надпись Не равно. Это происходит потому, что в этом случае сравниваются указатели, а не содержимое строк, а указатели здесь будут разные. Попытка сравнить строки с помощью оператора сравнения — весьма распространенная ошибка у начинающих. Для сравнения таких строк следует применять специальную функцию — StrComp
. Следующий пример, на первый взгляд, в плане сравнения ничем не отличается от только что рассмотренного (листинг 3.20).
Интервал:
Закладка: