В четвертом случае литерал также хранится в сегменте кода, но работы с указателем уже нет. Этот литерал занимает там пять байтов: один байт на длину и четыре — на символы. Переменная 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
на компакт-диске).
Листинг 3.18. Приведение литералов к типу 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.
Читать дальше
Конец ознакомительного отрывка
Купить книгу