Delphi: Загадочный тип PCHAR (исходники)

Источник: programmersclub

Здравствуйте, дельфисты! Сегодня вам поведую, что это за тип PCHAR. И как его корректно использовать. Этот тип упоминается во всех API функциях, которые принимают в качестве параметра какое-либо строковое значение.

Сначала я расскажу вам про тип string. Тип string является главным преимуществом языка Pascal над языком С. Именно из-за этого типа программы, написанные на Pascal, весят больше, чем программы, написанные на С. Все знают, что тип string является массивом, котором каждый элемент является типом CHAR (следовательно, юникодовский тип WideString - массив из WideChar). Только размер этого массива неизвестен заранее и при каждом присваивании его длина изменяется. Но так же можно и при объявлении ограничить размер строки. Но размер строки ограничивается только формально, потому что нельзя обратиться к элементу массива больше чем размер строки указанной в разделе var. Конечно, размер каждой строки должен быть не более 2ГБ, примерно 2 миллиарда символов (для widestring 1 миллиард символов, так как один символ задаётся 2 байтами). Так как строка это массив, следовательно, через квадратные скобки можно обращаться к каждому элементу массива.

Str:='programmersclub.ru';
Str[1]:='P';
Str[5]:='L';

После данных манипуляций переменная str будет равна 'ProgLammersclub.ru', замечу, что первый символ в строке имеет индекс 1. Так как тип string и тип array of char сходны следователь их можно присвоить друг к другу. Но, при присваивании переменной массива переменной строки будет ошибка, так как у массива мы жёстко задаём размер массива, а у строки мы не знаем длину даже при её ограничении.

Var
STR_ARR: ARRAY[1..60] of CHAR;
STR1:Srring;
STR2:String[20];
Begin
………………
STR_ARR:=str1;//ошибка
Str1:=STR_ARR:
Str2:=STR_ARR;//нет ошибки

Обращаться к 55 символу переменной str2 уже нельзя, но физически в памяти он существует. При получении указателя на строку возвращается адрес первого символа в строке, значит, такие выражения одинаковы:

P1:=@str;
P2:=@str[1];

Теперь тип PCHAR. Фактически тип PCHAR это указатель на тип CHAR. Это понятно и потому как он назван. По "программеским" правилам при объявлении нового типа типизированных указателей берётся тип, на который указывает указатель и спереди ставится буква P. Вот его объявление:

Type
PCHAR: ^ CHAR;// следовательно также и тип WideChar
PWIDECHAR: ^ WIDECHAR;

Ну, если этот тип указывает на только один символ, то, как же функции понимают параметры, которые мы передаём им. Всё очень просто. Каждая строка, переданная в качестве параметра какой либо функции должна иметь в конце символ #0. Функция по указателю находит первый символ строки и идёт дальше пока не наткнётся на символ #0. Delphi автоматизировала преобразование строки когда в параметре мы указываем саму строку: MessageBox(0,'привет','привет',0) здесь автоматика, а почему не автоматика при указывании переменных я не знаю. Мы всегда пишем

MessageBox(0,pchar(str),pchar(str),0);

Всё нормально. Тот же результат при использовании указателей.

MessageBox(0,@str,@str,0);

Это потому что массивы обычно заполняются нулями.
Пример 1:
Совсем другая история:

var
STR_ARR:array[1..2] of char;
STR:String[6];
begin
str_ARR[1]:='h';
str_ARR[2]:=#0;
STR:='22'+#0;
MessageBoxA(Handle,@STR,@str_ARR,MB_OK);

В сообщении перед двойкой стоит какой то символ. Это потому что нумерация в Delphi может начинаться с любого индекса, в данном случае она начинается 1, а 0 символ не используется. Значит то, что я вам сказал в начале это неправильно:
P1:=@str;
P2:=@str[1]; //эти указатели будут отличаться на 1;

А массив выводится нормально, потому что Delphi автоматически правит указатель на массив.
Пример 2:

Не знаю почему, но вместо двоек выводится не пойми что. Мистика!!!

var
STR_ARR:array[1..2] of char;
STR:String; //любая длина
begin
str_ARR[1]:='h';
str_ARR[2]:=#0;
STR:='22'+#0;
MessageBoxA(Handle,@STR,@str_ARR,MB_OK);
Пример 3:
var
STR_ARR:array[1..2] of char;
STR:String[4];
begin
str_ARR[1]:='h';
str_ARR[2]:=#0;
STR:='22';
MessageBoxA(Handle,@STR,@str_ARR,MB_OK);

Отсюда понятно что изначально все переменные заполняются нулями. И переменная STR тому не исключение.
Пример 4:
Интересная ситуация:
var
STR_ARR:array[1..2] of char;
STR:String[4];
begin
str_ARR[1]:='h';
str_ARR[2]:='s';
STR:='22'+#0;
MessageBoxA(Handle,@STR,@str_ARR,MB_OK);

Как видите в заголовке есть символы 'hs' потом квадратик потом двойки. Всему есть разумное объяснение. Строка STR_ARR не кончается нулём, следовательно, функция не нашла нуль только в конце строки STR_ARR и пошла дальше к переменной STR, поэтому строка STR_ARR получилась такой длинной. Квадратик в после строки 'hs' понятен из первого примера.

Вот такие пироги. Изначально казалось, что тип string проще и лучше, а получилось как всегда! Тип string принёс нам массу неприятностей. Поэтому C++ намного популярнее Delphi. Вот такими словами кончается моя очередная статья.

Руслан Аблязов

Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=1269