Главная страница
    Top.Mail.Ru    Яндекс.Метрика
Форум: "Потрепаться";
Текущий архив: 2004.05.02;
Скачать: [xml.tar.bz2];

Вниз

FAQ: PChar и String   Найти похожие ветки 

 
Piter ©   (2004-04-04 17:56) [0]

Написал тут про эти типы данных для FAQ"а.
Если есть замечания - высказывайтесь:

Итак, что PChar, что String - структуры для хранения текстовой информации. Рассмотрим поподробнее принципы их работы.

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

00452130    FF EB F0 8B E5 5D C3 00    ялр<e]Г.
00452138    D2 E5 F1 F2 00 00 00 00    Тест....
00452140    55 8B EC 33 C0 55 68 65    U<м3АUhe

Это стандартный вид CPU window в области просмотра памяти.
Первая колонка - это адрес первого значения в шестнадцатеричном формате. Соответственно, по адресам:
$00452130 располагается байт FF ("я")
$00452131 располагается байт EB ("л")
$00452132 располагается байт F0 ("р")
$00452138 располагается байт D2 ("Т")
$0045213A располагается байт F1 ("с")

Вторая колонка - значения памяти в шестнадцатеричном формате.
Третья колонка - значения памяти в текстовом формате. Не удивляйтеь, что байт 00 в текстовом отображении имеет вид "." точки. На самом деле, точка как знак равна байту 2E. Но надо же как-то отображать байт 00, который не имеет текстового представления?

Итак, допустим наша PChar указывает на адрес $00452138. Там располагается байт D2 или в текстовом представлении символ "Т". Отлично, это означает что начиная с этого символа и далее идет наша строка. Но остается вопрос - а как определить где строка кончается? Для этого в конце строки ставится символ #0 или, что по другому, байт 00.
В результате, компилятор смотрит на что указывает PChar... ага, это адрес $00452138, по нему находится символ "Т", потом смотрит далее. Символ "е". Потом "с". Далее, доходит до последнего символа "т". Еще далее видит байт 00. Опа, значит это конец строки.
Значит, строка занимает в памяти 5 байт, включая конечный символ, и занимает адреса в памяти процесса $00452138-$0045213C.

Здесь стоит упомянуть про одну вещь. PChar - это указатель на строку (точнее, на первый символ строки). Но если ввести переменную P:PChar, то @P не будет указывать на первый символ строки. Он будет указывать на область памяти, где хранится указатель на строку.
С первого раза достаточно путано, но на самом деле все логично.
В нашем примере p=$00452138, а вот @p=$12F5B4.
Если перейти по адресу в памяти $12F5B4, то мы увидим:

0012F5B4    38 21 45 00 30 1E C0 00
0012F5BС    АС А6 12 00 А6 29 43 00

Так как указатель в 32 битных системах занимает те самые 32 бита (то есть 4 байта), то значения по адресу @p=$0012F5B4 равны: 38 21 45 00. А если вспомнить, что числа на платформе  x86 хранятся "задом наперед", то получается 00 45 21 38! А это и есть адрес $00452138, по которому находится непосредственно наша строка (а точнее первый символ "Т").

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

String
Сначала определимся, что String - это тип, зависящий от директив компилятора. При директиве {$H+}, которая установлена по умолчанию, String интерпретируется как AnsiString, если {$H-} - то как ShortString.
Будем рассматривать ShortString и AnsiString.

Итак, PChar не лишен недостатков. Например, вдруг вам захочется сохранить байт 00 в строке (вообще, он не может встречаться в тексте, поэтому и выбран в качестве завершающего символа, но тем не менее). С PChar"ом ничего не получится.
Рассмотрим:


var P:PChar
   s:string;      
...
begin
 P := "Я иду" + #0 + " домой";
 s:=p;


Чему будет равен s после выполнения последней строчки? Правильно: "Я иду". И хотя в памяти по адресу P будет строка "Я иду#0 домой", но компилятор не поймет, что " домой" тоже является частью P, он встретил байт 00, значит все, это конец PChar. И при присвоении s:=p присвоит s значение "Я иду".
На примере видно, как работает неявное приведение типов - строчка P := "Я иду" + #0 + " домой"; не имеет смысла, так как P это адрес в памяти и он не может быть равен двенадцати символам. Но Дельфи все понимает правильно, она присваивает P адрес символа "Я" в памяти.

Теперь подумаем - как побороть недостаток PChar"а? Выход прост - пусть строка имеет такой формат: <количество_символов><сама_строка>.
Именно такой формат имеет ShortString. Первый байт этой строки указывает количество последующих символов. Таким образом, можно хранить любые данные, компилятор не остановится, увидев завершающий символ, так как конец строки определяется по другому признаку. Если длина 15 - то он будет обрабатывать именно 15 символов (байт), независимо от того, что в них хранится. ShortString не имеет завершающего символа в конце, поэтому не совместим с WinApi, где нужно передавать строки именно с завершающим символом.

Недостаток типа ShortString заключается в его названии :) Он достаточно короткий. Так как на длину строки отводится 1 байт, то он не может принимать значение более 255, а значит ShortString не может хранить более 255 символов.

Плавно переходим к типу AnsiString (по умолчанию этому типу и равен String). Тут все достаточно сложно, данная строка хранит размер выделенной памяти, счетчик ссылок, число символов. Причем, тип недокументирован, а значит его структура может изменяться от одной версии Дельфи к другой. В любом случае, внутренняя реализация скрыта от программиста и использовать тип AnsiString очень удобно.
На размер строки отводится 4 байта, соответственно, AnsiString может хранить огромное количество символов (порядка 2 Гб), что покроет с лихвой любые нужды.
К тому же, AnsiString совместим с типами с завершающими символами в конце. Это означает, что его можно использовать для вызова функций WinApi.

Преобразования типов
Итак, как преобразовать тип Pchar к String (AnsiString) и наоборот?

<см. продолжение>


 
Piter ©   (2004-04-04 17:57) [1]

String->PChar
Рассмотрим на примере вызовов функций WinApi, которые принимают параметры типа PChar. А мы работаем в Дельфи и хотим использовать string.

Рассмотрим вызов функции:
function MessageBox(hWnd: HWND; lpText, lpCaption: PChar; uType: UINT): Integer; stdcall; которая вызывает на экране появление бокса с сообщением.

Параметры lpText и lpCaption являются PChar.
Указываем компилятору явно преобразовать AnsiString в Pchar:

procedure TForm1.Button1Click(Sender: TObject);
var s1, s2:string;
begin
s1:="text";
s2:="caption";
MessageBox(0,PChar(s1),PChar(s2),0);
end;


Можно сделать проще:
MessageBox(0,"text","caption",0);
при этом компилятор сам приведет текстовые строки к формату PChar и передаст в функцию MessageBox.

Тут все ясно. Но что делать, когда нужно передать PChar для того, чтобы система заполнила его каким-нибудь значением?

Рассмотрим вызов функции GetWindowsDirectory(lpBuffer: PChar; uSize: UINT): UINT; которая возвращает путь к директории Windows. А точнее записывает его в передаваемую переменную lpBuffer, а uSize указывает размер этого буфера.

procedure TForm1.Button1Click(Sender: TObject);
var s:string;
   Length: integer;
begin
setlength(s,MAX_PATH);
Length:=GetWindowsDirectory(PChar(s),MAX_PATH);
if Length=0 then
 showmessage("Не удалось вызвать функцию GetWindowsDirectory")
else
 setlength(s,Length);
 showmessage(s);
end;


В общем, тоже самое, но только НЕ ЗАБЫВАЕМ ВЫДЕЛИТЬ ПАМЯТЬ для переменной S перед использованием - setlength(s,MAX_PATH).
После вызова GetWindowsDirectory в переменной S также будет MAX_PATH символов. Причем, большинство их них пробелы, которые вовсе не нужны. Поэтому, перед использованием такой переменной отрежем ненужные символы. Так как GetWindowsDirectory возвращает количество символов, то код очевиден: setlength(s,Length);
В общем, можно обойтись и без переменной Length:

procedure TForm1.Button1Click(Sender: TObject);
var s:string;
begin
setlength(s,MAX_PATH);
setlength(s,GetWindowsDirectory(PChar(s),MAX_PATH));
if s="" then
 showmessage("Не удалось вызвать функцию GetWindowsDirectory")
else
 showmessage(s);
end;


Ну а теперь рассмотрим просто пример конвертирования:


var p:PChar;
...
p:=PChar(s); // где-то есть переменная s и в ней хранится некая строка


При этом p начинает указывать на ту же область памяти, что и s. И все будет замечательно, пока с s ничего не происходит. Но вот например:

var p:PChar;
   s:string;
begin
setlength(s,MAX_PATH);
setlength(s,GetWindowsDirectory(PChar(s),MAX_PATH));
p:=PChar(s);
s:=s+"SomeText";


Итак, в s заносится путь к директории windows. В памяти таким образом есть некая строка, допустим "c:\windows", которой и равна s.
После чего идет p:=PChar(s) - при этом p начинает указывать на ту же область памяти, где находится строка "c:\windows".
Потом s присваивается другое значение. А точнее к s должно быть прибавлено "SomeText". При этом менеджер памяти анализирует ситуацию, можно ли приписать данный текст прямо в ту же область памяти. Если память после "c:\windows" не занята, то он приписывает нужное количество символов. При этом S будет указывать по прежнему на тот же адрес в памяти, только увеличится счетчик символов. Соответстенно, и с P, указывающим туда же, будет все нормально. И после завершения кода P будет указывать на строчку "c:\windowsSomeText".

Но если невозможно подряд разместить все новые символы, то менеджер памяти будет искать "дырку" в памяти, свободную для размещения всех символов. При этом память, где хранится текущее "c:\windows" будет объявлена свободной, резервируется место для новой строки, туда записывается значение "c:\windowsSomeText". А P по прежнему указывает на старый участок памяти! Скорее всего, там будет продолжать хранится строчка "c:\windows", потому как память никто не зачищал, но эта память является свободной. И в случае чего, данное место будет зарезервировано под иные нужды и там разместятся некие данные, в результате P будет указывать на черт знает что.
Для демонстрации можно выполнить такой пример:

var p:PChar;
   s:string;
   s2:string;
begin
setlength(s,MAX_PATH);
setlength(s,GetWindowsDirectory(PChar(s),MAX_PATH));
p:=PChar(s);
setlength(s2,1);
s:=s+"SomeText";
showmessage(p);
end;


Тоже самое, но добавилась строчка setlength(s2,1).
Итак, p:=PChar(s) Что s, что P указывают на одну область памяти.
А потом setlength(s2,1);  - если ничего неожиданного не случится, то менеджер памяти Дельфи выделит под переменную s2 место сразу после s.
В результате, следующая строчка s:=s+"SomeText"; будет обработана не так, как в предыдущем примере.
Места для добавления "SomeText" по старому адресу уже не хватит (ведь после s идет сразу s2), будет выбрано новое место и s перенесена туда. А P по прежнему ссылается на старое место! В результате, после выполнения этого кода, P скорее всего будет ссылаться на область памяти со значением "c:\windows", а не "c:\windowsSomeText"! И на экран showmessage выведет именно "c:\windows". И только потому, что эта свободная память, на которую указывает P, не была занята чем-то другим.

PChar->String

ну тут все просто. Если P указывает на некую строчку в памяти:

var s:string;
...
s:=p;


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


 
Gero ©   (2004-04-04 18:26) [2]

Неплохо, но в "String->PChar" воды многовато.


 
Ломброзо ©   (2004-04-04 18:31) [3]

ЗдОрово. Имхо - раз уж взялись за труд писать мануалы, то отчего бы не начать с массивов и указателей на них - ведь такие типы как LPDWORD, LPVOID и пр. в справке по WinAPI встречаются не реже и вопросов у начинающих писать на Pascal вызывают не меньше, чем TCHAR, LPSTR и LPTSTR :-)
А строки - это ведь частный случай массивов...


 
DrPass ©   (2004-04-04 19:03) [4]

Если бы те, кто спрашивает на форуме "как преобразовать string в pchar", читали бы факи, хелпы и статьи, то они бы не спрашивали. Так что фигня все это.


 
Ihor Osov'yak ©   (2004-04-04 19:04) [5]

> В результате, компилятор смотрит на что указывает

Компилятор не смотрит, компилятор компилирует. А "cмотрит", то бишь анализирует, код, или нами написанный, или соотв. библиотечной функции в рантайме

> но компилятор не поймет, что " домой" тоже является частью P, он встретил байт 00, значит все, это конец PChar

Те же пироги. Не компилятор, а функции, работающие с PChar

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

Не упомянуто то, что шортстринг размещаются статически, AnsiString - динамически. Также не затронут вопрос о том, что AnsiString - существа с управляемым временем жызни. Не упомянуто о широких строках.

Зы. Критиковать легче, чем работать. Это я знаю. В том числе и по себе :-).


 
Piter ©   (2004-04-04 19:11) [6]

Gero (04.04.04 18:26) [2]
но в "String->PChar" воды многовато


Можно конкретно указать место, где вода?

Ломброзо (04.04.04 18:31) [3]
раз уж взялись за труд писать мануалы


да не брался я ни за какой труд. Это для FAQ"а... мини-статья типа...


 
Piter ©   (2004-04-04 19:27) [7]

Ihor Osov"yak (04.04.04 19:04) [5]
Компилятор не смотрит, компилятор компилирует. А "cмотрит", то бишь анализирует, код, или нами написанный, или соотв. библиотечной функции в рантайме


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

Ihor Osov"yak (04.04.04 19:04) [5]
Написанно очень путанно


а нельзя ли поподробнее. Почему путанно? По-моему, достаточно логично (может, ты слишком быстро читал?). Что такое PChar, что такое string, потом их взаимное преобразование. Есть идеи по структуризации? Я с большой радостью выслушаю... что поменять местами, какие места наиболее путанные? :)

Ihor Osov"yak (04.04.04 19:04) [5]
Не упомянуто то, что шортстринг размещаются статически


да вообще про ShortString просто так наплел. Ведь посвящено то теме String и PChar. Причем, под String подразумевается AnsiString, так как ShortString очень мало применяется. Я его просто упомянул в связи с директивой {$H-} .

Ihor Osov"yak (04.04.04 19:04) [5]
Также не затронут вопрос о том, что AnsiString - существа с управляемым временем жызни. Не упомянуто о широких строках


эй, эй. Ты, наверное, подумал, что это статья по строковым типам в Дельфи? Но это лишь пояснение к FAQ"у по поводу вопроса "Как преобразовать PChar в String и наоборот" так сказать мини-ликбез.


 
Игорь Шевченко ©   (2004-04-04 20:50) [8]


> Тут все ясно. Но что делать, когда нужно передать PChar
> для того, чтобы система заполнила его каким-нибудь значением?


function MyGetWinDir: string;
var
 Buffer: array[0..MAX_PATH] of char;
begin
 if GetWindowsDirectory(Buffer, SizeOf(Buffer)) = 0 then
   RaiseLastWin32Error;
 Result := Buffer;
end;


 
Piter ©   (2004-04-04 20:56) [9]

Игорь Шевченко (04.04.04 20:50) [8]

Это вы к чему? Вы случайно не подумали, что я в статье задаю такой вопрос? Я типа задал вопрос от лица читателя и тут же на него отвечаю :)


 
Игорь Шевченко ©   (2004-04-04 21:02) [10]

Piter ©   (04.04.04 20:56)

Это я к тому, что существует и такое решение. Может и его стоит упомянуть, как альтернативу Setlength, как считаешь ?


 
Gero ©   (2004-04-04 21:04) [11]


> Можно конкретно указать место, где вода?

Имхо просто покороче надо было писАть.
Краткость - сестра таланта (c)


 
Piter ©   (2004-04-04 21:10) [12]

Игорь Шевченко (04.04.04 21:02) [10]

а-а-а, понял. Ну не знаю, можно, конечно. Но с другой стороны, речь идет о String и PChar.
А совсем с другой стороны можно упомянуть, что любая текстовая строка - это массив символов. И array[0..MAX_PATH] of char; массив символов. Соответственно, его можно использовать как строку. И как аналог PChar при вызове WinApi.
Как вы считаете?


 
Игорь Шевченко ©   (2004-04-04 21:20) [13]

Piter ©   (04.04.04 21:10)


> что любая текстовая строка - это массив символов. И array[0..MAX_PATH]
> of char; массив символов


Я бы разграничил длинные строки, у которых кроме массива символов еще есть управляющие структуры и собственно, массив [0..n] of Char, который примечателен тем, что его использование в качестве параметра совместимо с объявлением параметра типа PChar.
А раз он совместим с PChar, то его можно безболезенно присваивать AnsiString (точно также, как array[0..n] of WideChar можно присваивать переменной типа WideString).


 
Nous Mellon ©   (2004-04-04 22:24) [14]

Спасибо Тезка. Узнал для себя много нового.
А "критиков" не слушайте - ломать не строить :)
Еще раз спасибо. Отлично пишите, кстати.
Буду оформлять ваши ЧАВО у себя на винте в коллекцию статей.


 
Piter ©   (2004-04-04 23:08) [15]

Nous Mellon (04.04.04 22:24) [14]

Ага, ну вот буду тебя мучать. Что в статье узнал нового, о чем раньше не подозревал? Что лучше осветоить более подробно? Какие непонятности?


 
Ihor Osov'yak ©   (2004-04-04 23:27) [16]

2 Piter ©   (04.04.04 19:27) [7]

> ты предлагаешь изменить на

Было
>Итак, что PChar, что String - структуры для хранения текстовой информации. Рассмотрим поподробнее принципы их работы.

Может стать:

Так исторически  сложилось, что в мире делфи в общем случае сосуществуют два способа представления строк. Согласно первого способа строка представляет собой последовательность символов (эту последовательность будем называть телом строки), для которой где-то хранится информация о длине строки (во всех практических реализациях - перед телом строки – но довольно часто это обстоятельство недокументированно, следовательно на это ставку делать в большинстве случаев нельзя).  Второй способ примечателен тем, что в явном виде нигде не фигурирует длинна строки, как конец строки используется специальный символ с кодом $00.  Первый способ изначально использовался в старом добром паскле, второй более характерен для мира с.
 Это собственно о способе представления. Теперь же как переменные нашего программного кода  связаны с этими представлениями.   Сразу следует оговорить две вещи. Первая – что в делфи String можно считать неким виртуальным типом, так как в конкретном случае в зависимости от кода или установок компилятора вместо String используются “реально” существующие типы , те же AnsiString или ShortString (об этом далее). Второе - в большинстве случаев эти строчные  переменные с точки зрения кода, построенного компилятором   есть не что иное, как указатель на тело строки (забегая наперед, следует отметить что старая паскалевская строка, известная в мире делфи как ShortString, либо строка с определенной длинной (“усеченный” вариант ShortString) размещаются компилятором  все же статически. То есть в коде, построенном компилятором, не отводится места для указателя на тело ShortString, так как соответствующая переменная  и есть телом короткой строки.
Может показаться. что все эти подробности излишни,  но если их понять, то сразу исчезнет большинство  тумана, который иногда наблюдается в районе приведения и преобразований String и PСhar.


Было:
> В результате, компилятор смотрит на что указывает PChar... ага, это адрес $00452138, по нему находится символ "Т", потом смотрит далее. Символ "е". Потом "с". Далее, доходит до последнего символа "т". Еще далее видит байт 00. Опа, значит это конец строки.
 И хотя в памяти по адресу P будет строка "Я иду#0 домой", но код, ориентированный на работу с PChar строками компилятор не поймет, что " домой" тоже является частью P, он встретил байт 00, значит все, это конец PChar. И при присвоении s:=p присвоит s значение "Я иду".


Может стать:
Во время выполнения программы, код, ориентированный на работу с PChar, анализирует, на что указывает соответствующий указатель.  Ага, это адрес $00452138, по нему находится символ "Т", потом смотрит далее. Символ "е". Потом "с". Далее, доходит до последнего символа "т". Еще далее видит байт 00. Опа, значит это конец строки.
 И хотя в памяти по адресу P будет строка "Я иду#0 домой", но компилятор не поймет, что " домой" тоже является частью P, он встретил байт 00, значит все, это конец PChar. И при присвоении s:=p присвоит s значение "Я иду".


Может стать:



И так далее….

>>Написанно очень путанно

>а нельзя ли поподробнее. Почему путанно? По

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

2 Nous Mellon ©   (04.04.04 22:24) [14]

А вот здесь Вы не правы. Будет меньше слушать, будет пользы меньше для Вас же.. А послушает - будет  и пользы больше..

> эй, эй. Ты, наверное, подумал, что это статья по строковым типам в Дельфи

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


 
Alex Konshin ©   (2004-04-04 23:42) [17]

Есть еще и такая статья, которая на самом деле взята из моего сообщения FIDO в старые лохматые года:
http://akzhan.midi.ru/devcorner/akdeltnt-content/akdeltnt-0033.html


 
Ihor Osov'yak ©   (2004-04-05 00:01) [18]

2  Piter

Насчет преамбулы, может я не прав, скажем для faq это жирновато.. Может и нет..
Но вот то, что вместо рантайма "компилятор смотрит" используешь - это действительно никуда..  Да и текст немного непоследователен.. Сравни со силем того, на что указал Кощин - разницу чувствуешь? Хотя сама идея заметки - может быть..

Успехов.


 
Piter ©   (2004-04-05 00:55) [19]

Ihor Osov"yak (05.04.04 00:01) [18]
Насчет преамбулы, может я не прав, скажем для faq это жирновато


вот я тоже так считаю. И так много получилось

Ihor Osov"yak (05.04.04 00:01) [18]
Но вот то, что вместо рантайма "компилятор смотрит" используешь - это действительно никуда


ок, учту твой вариант.


 
Anatoly Podgoretsky ©   (2004-04-05 00:57) [20]

Сейчас это ближе к статье, чем к Чаво, для чаво надо резко уменьшить.


 
Nous Mellon ©   (2004-04-05 06:50) [21]


> Что в статье узнал нового, о чем раньше не подозревал? Что
> лучше осветоить более подробно? Какие непонятности?

Честное пожарное не знал то как именно происходит добавление например строки к текущей строке если не хватает памяти. Неопнятностей нет у все отлично освещено. Жду новых СТАТЕЙ. НЕ надо ЧАВО -пиши статьи у тебя это отлично получается.


 
Думкин ©   (2004-04-05 09:03) [22]

По поводу строк неплохо есть у Калверта - "Дельфи 2". Кажется, целая глава посвящена.


 
panov ©   (2004-04-05 10:15) [23]

>Piter

Так держать!

Статьи лучше в другой раздел, конечно.

В FAQ после примера можно просто помещать ссылку на статью.


 
Sergey_Masloff   (2004-04-05 10:19) [24]

Думкин ©   (05.04.04 09:03) [22]
>По поводу строк неплохо есть у Калверта - "Дельфи 2". Кажется, >целая глава посвящена.
Угу. Кстати видел тут эту книжку в Олимпийском по 30 р. ;-) Да, конечно, устарела но на 60% вопросов форума ответы в ней есть.


 
Тимохов ©   (2004-04-05 10:22) [25]

Здорово это все, конечно (честное слово, Piter - респект).
Но вы уверены, что это фак?

Если у Л хватит выдержки прочесть этот хороший текст, то он не Л, а если он не Л, то он просто обязан прочеть раздел хелпа, посвященный строкам, из object pascal reference (там все это описано). Если же он Л, то прочесть и этот текст ему не суждено.

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

:((((

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


 
Piter ©   (2004-04-05 10:25) [26]

Anatoly Podgoretsky (05.04.04 00:57) [20]
Сейчас это ближе к статье, чем к Чаво, для чаво надо резко уменьшить.


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


 
Piter ©   (2004-04-05 10:26) [27]

Переписал тут стать. Попытался сделать более понятной:
----------------------------------------------------------------------------------------------------------------

В данной мини-статье предлагаю вам рассмотреть поподробнее типы данных PChar и String, так как по ним возникает очень много вопросов в интернете.
Оба этих типа предназначены для хранения произвольного текста. Текст - это набор (масcив) символов в памяти, только разные типы по разному производят хранение этого массива.

PChar
Устройство PChar"а самое элементарное, которое может придти в голову. В памяти компьютера хранится последовательный набор символов, а сам PChar, который является указателем, ссылается на первый символ в памяти (то есть, хранит адрес байта в памяти процесса, который является первым символом строки). Этот символ и последующие и будут составлять строку.
Рассмотрим некий участок памяти:

ялр<e]Г.Тест....U<м3АUhe

В данном месте хранится строка "Тест" плюс я еще выделил несколько байт слева и справа.
Допустим, у буквы "Т" адрес в памяти $00452138. Соответственно, в данном случае PChar и будет равен адресу $00452138, по которому находится первый символ строки.
Но возникает вопрос - а где конец строки? На начало указыват сам PChar, а вот конец строки определяется по завершающему символу. Для PChar он равен #0 в текстовом представлении или, что тоже самое, байту $00 (в примере нулевые байты отображаются как точка ".").

В результате, строка занимает 5 байтов, включая завершающий символ, и занимает адреса в памяти процесса $00452138-$0045213C.

Здесь стоит упомянуть про одну вещь. PChar - это указатель на строку (точнее, на первый символ строки). Но если ввести переменную P:PChar, то @P не будет указывать на первый символ строки. Он будет указывать на область памяти, где хранится указатель на строку.
С первого раза достаточно путано, но на самом деле все логично.
В нашем примере p=$00452138, а вот @p=$0012F5B4.
Если перейти по адресу в памяти $0012F5B4, то мы увидим:

38 21 45 00 30 1E C0 00 (это в шестнадцатеричном виде)

Так как указатель в 32 битных системах занимает те самые 32 бита (то есть 4 байта), то значения по адресу @p=$0012F5B4 равны: 38 21 45 00. А если вспомнить, что числа на платформе  x86 хранятся "задом наперед", то получается 00 45 21 38! А это и есть адрес $00452138, которому и равен P и по которому находится непосредственно наша строка (а точнее первый символ "Т").

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

String
Сначала определимся, что String - это тип, зависящий от директив компилятора. При директиве {$H+}, которая установлена по умолчанию, String интерпретируется как AnsiString, если {$H-} - то как ShortString.

Итак, PChar не лишен недостатков. Например, вдруг вам захочется сохранить байт 00 в строке (вообще, он не может встречаться в тексте, поэтому и выбран в качестве завершающего символа, но тем не менее). С PChar"ом ничего не получится.
Рассмотрим:


var P:PChar      
...
P := "Я иду" + #0 + " домой";


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

Теперь подумаем - как побороть недостаток PChar"а? Выход прост - пусть строка имеет такой формат:
<количество_символов><сама_строка>.
Именно такой формат имеет ShortString. Первый байт этой строки указывает количество последующих символов. Таким образом, можно хранить любые данные, компьютер не остановится, увидев завершающий символ, так как конец строки определяется по другому признаку. Если длина 15 - то он будет обрабатывать именно 15 символов (байт), независимо от того, что в них хранится. ShortString не имеет завершающего символа в конце, поэтому не совместим с WinApi, где нужно передавать строки именно с завершающим символом.

Недостаток типа ShortString заключается в его названии :) Он достаточно короткий. Так как на длину строки отводится 1 байт, то он не может принимать значение более 255, а значит ShortString не может хранить более 255 символов.

Плавно переходим к типу AnsiString (по умолчанию этому типу и равен String). Тут все достаточно сложно, данная строка хранит размер выделенной памяти, счетчик ссылок, число символов. Мы не будет рассматривать побайтово его структуру в памяти, тем более, что тип недокументирован, а значит его структура может изменяться от одной версии Дельфи к другой. В любом случае, внутренняя реализация скрыта от программиста и использовать тип AnsiString очень удобно.
На размер строки отводится 4 байта, соответственно, AnsiString может хранить огромное количество символов (порядка 2 Гб), что покроет с лихвой любые нужды.
К тому же, AnsiString совместим с типами с завершающими символами в конце. Это означает, что его можно использовать для вызова функций WinApi.
Также стоит упомянуть, что ShortString статический тип, то есть под него однажды выделяют место в памяти и он так и остается там по одному адресу.
AnsiString же тип динамический, эта строка может менять свою длину и "перемещаться" по памяти в поисках наилучшего места для размещения. То есть AnsiString может менять свой адрес в памяти при изменении строки.

Преобразования типов
Итак, как преобразовать тип Pchar к String (AnsiString) и наоборот?

String->PChar
Рассмотрим на примере вызовов функций WinApi, которые принимают параметры типа PChar. А мы работаем в Дельфи и хотим использовать string.

<см. продолжение>


 
Piter ©   (2004-04-05 10:27) [28]

Рассмотрим вызов функции:
function MessageBox(hWnd: HWND; lpText, lpCaption: PChar; uType: UINT): Integer; stdcall;
которая вызывает на экране появление бокса с сообщением.

Параметры lpText и lpCaption являются PChar.
Указываем компилятору явно преобразовать AnsiString в Pchar:

procedure TForm1.Button1Click(Sender: TObject);
var s1, s2:string;
begin
s1:="text";
s2:="caption";
MessageBox(0,PChar(s1),PChar(s2),0);
end;


Можно сделать проще:
MessageBox(0,"text","caption",0);
при этом компилятор сам приведет текстовые строки к формату PChar и передаст в функцию MessageBox.

Тут все ясно. Но что делать, когда нужно передать PChar для того, чтобы система заполнила его каким-нибудь значением?

Рассмотрим вызов функции
GetWindowsDirectory(lpBuffer: PChar; uSize: UINT): UINT;
которая возвращает путь к директории Windows. А точнее записывает его в передаваемую переменную lpBuffer, а uSize указывает размер этого заготовленного буфера.

procedure TForm1.Button1Click(Sender: TObject);
var s:string;
   Length: integer;
begin
 setlength(s,MAX_PATH);
 Length := GetWindowsDirectory(PChar(s),MAX_PATH);
 setlength(s,Length);
 ...


В общем, тоже самое, но только НЕ ЗАБЫВАЕМ ВЫДЕЛИТЬ ПАМЯТЬ для переменной S перед использованием:
setlength(s,MAX_PATH)
После вызова GetWindowsDirectory в строке S также будет находится MAX_PATH символов. Причем, большинство их них пробелы, которые вовсе не нужны. Поэтому, перед использованием такой строки отрежем ненужные символы. Так как GetWindowsDirectory возвращает количество символов, то код очевиден:
setlength(s,Length);
Можно обойтись и без переменной Length:

procedure TForm1.Button1Click(Sender: TObject);
var s:string;
begin
 setlength(s,MAX_PATH);
 setlength(s,GetWindowsDirectory(PChar(s),MAX_PATH));
 ...


Ну а теперь рассмотрим просто пример конвертирования:


var p:PChar;
...
p:=PChar(s); // где-то есть переменная s и в ней хранится некая строка


При этом p начинает указывать на ту же область памяти, что и s. И все будет замечательно, пока с s ничего не происходит. Но вот например:

var p:PChar;
   s:string;
begin
 setlength(s,MAX_PATH);
 setlength(s,GetWindowsDirectory(PChar(s),MAX_PATH));
 p:=PChar(s);
 s:=s+"SomeText";
 ...


Итак, строчками
setlength(s,MAX_PATH);
setlength(s,GetWindowsDirectory(PChar(s),MAX_PATH));

в s заносится путь к директории windows. В памяти таким образом появляется некая строка, допустим "c:\windows", которой и равна s.
После чего идет
p:=PChar(s);
при этом p начинает указывать на ту же область памяти, где находится строка "c:\windows".
Потом s присваивается другое значение
s:=s+"SomeText";
А точнее к s должно быть прибавлено "SomeText". При этом менеджер памяти анализирует ситуацию, можно ли приписать данный текст прямо в ту же область памяти. Если память после "c:\windows" не занята, то он приписывает нужное количество символов. При этом S будет указывать по прежнему на тот же адрес в памяти, только увеличится счетчик символов. Соответстенно, и с P, указывающим туда же, будет все нормально. И после завершения кода P будет указывать на строчку "c:\windowsSomeText".

Но если невозможно подряд разместить все новые символы, то менеджер памяти будет искать "дырку" в памяти, свободную для размещения всех символов. При этом память, где хранится текущее "c:\windows" будет объявлена свободной, резервируется место для новой строки в другом месте, туда записывается значение "c:\windowsSomeText" и S начинает указывать на новый участок памяти, где хранится новое значение строки. А P по прежнему указывает на старый участок памяти! Скорее всего, там будет продолжать хранится строчка "c:\windows", потому как память никто не зачищал, но эта память является уже свободной. И в случае чего, данное место будет зарезервировано под иные нужды и там разместятся произвольные данные, в результате P будет указывать на черт знает что.
Для демонстрации можно выполнить такой пример:

var p:PChar;
   s:string;
   s2:string;
begin
 setlength(s,MAX_PATH);
 setlength(s,GetWindowsDirectory(PChar(s),MAX_PATH));
 p:=PChar(s);
 setlength(s2,1);
 s:=s+"SomeText";
end;


Тоже самое, но добавилась строчка setlength(s2,1);
Рассмотрим еще раз:
p:=PChar(s)
Что s, что P указывают на одну и ту же область памяти.
А потом
setlength(s2,1);
если ничего неожиданного не случится, то менеджер памяти Дельфи выделит под переменную s2 место сразу после s.
В результате, следующая строчка
s:=s+"SomeText";
будет обработана не так, как в предыдущем примере.
Места для добавления "SomeText" по старому адресу уже не хватит (ведь после s идет сразу s2), будет выбрано новое место и s перенесена туда, а "старая" память освобождена. Но P по прежнему ссылается на старое место! После выполнения этого кода, P скорее всего будет указывать на область памяти со значением "c:\windows" (но не факт, так как эта область памяти является свободной и может быть занята чем угодно), а не "c:\windowsSomeText"!

PChar->String

ну тут все просто. Если P указывает на некую строчку в памяти:

var s:string;
...
s:=p;


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


 
Anatoly Podgoretsky ©   (2004-04-05 10:30) [29]

Данный раздел FAQ может быть очень коротким

P := PChar(S);
S := P; // PChar(P)

С примером передачи в АПИ функции и парой слов
Также бы добавил, но модернизированый пример из [8]

Модернизация должна касаться следующего
1. Преобразование для string
2. вариант для массива
3. вариант для динамически распределенной памяти

с краткими комментариями


 
Verg ©   (2004-04-05 10:38) [30]

Мысль вслух: человек, который в состоянии вдумчиво и внимательно прочесть статью, никогда не задаст такого вопроса, после которого его захочется послать в FAQ..... справедливо и обратное утверждение...
Т.е. где же он раньше был? Т.е. до delphimaster.ru было сплошное мракобесие?
Тоны книг про и по Делфи и Паскалю - это все прошло как-то мимо, и только здесь наступит для него "просветление". Ага.
Или что, типа тяжелое детство? Хлеба не было и папа не давал денег на книжки... только на интернет?

Ленность - мотив номер ОДИН. Разжуйте мне эту "еду", а то мне лень самому.

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


 
Piter ©   (2004-04-05 10:47) [31]

Тимохов (05.04.04 10:22) [25]
Если у Л хватит выдержки прочесть этот хороший текст, то он не Л, а если он не Л, то он просто обязан прочеть раздел хелпа, посвященный строкам, из object pascal reference (там все это описано). Если же он Л, то прочесть и этот текст ему не суждено


Можно также сказать, что лучше не помогать людям в программировании. Если он хороший программист - то сам разберется. А если плохой - ну и нафига ему помогать? Так нельзя...

Очень бы хотелось послушать мнение Юрия Зотов по данной статье. Он ведь тоже не чужд преподавательской нотки :)


 
Verg ©   (2004-04-05 10:57) [32]


> Piter ©   (05.04.04 10:47) [31]


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

Или что - цель написать такой FAQ, чтобы форумы опустели? :))
(тапочки держуться за пузико :))


 
Игорь Шевченко ©   (2004-04-05 10:59) [33]


> Он больше нужен самим авторам ответов в нем:  формулирование
> мысли - есть способ самосовершенствования.


Между прочим, тоже неплохо. Я за два года, отвечая в форумах, многому научился.


 
Verg ©   (2004-04-05 11:06) [34]


> Игорь Шевченко ©   (05.04.04 10:59) [33]
>
> > Он больше нужен самим авторам ответов в нем:  формулирование
>
> > мысли - есть способ самосовершенствования.
>
>
> Между прочим, тоже неплохо. Я за два года, отвечая в форумах,
> многому научился.


Целиком ЗА.
Это, по крайней мере, единственное чем я объясняю для себя сам факт существования этого(тих) форумов.


 
Anatoly Podgoretsky ©   (2004-04-05 11:35) [35]

Игорь Шевченко ©   (05.04.04 10:59) [33]
Нечто подобное я советовал Марсеру, по поводу как повысить уровень знаний - отвечай в форумах и держи удар.


 
Piter ©   (2004-04-05 23:33) [36]

Юрий Зотов, ну обратите внимание на эту ветку...


 
Piter ©   (2004-04-06 18:57) [37]

Игорь Шевченко (05.04.04 10:59) [33]
Я за два года, отвечая в форумах, многому научился


ну думаю еще большему вы научились отвечая в FIDO

Verg (05.04.04 10:38) [30]
FAQ, даже очень подробный со статьями, ничего не изменит


гениальное мнение. А что ты собственно здесь делаешь? Ты отвечаешь на вопросы... а зачем? Это ничего не изменит, придут новые люди и задатут тебе те же вопросы. И ты опять будешь отвечать... а потом придут еще одни новые, и, возможно, ты уже просто будешь делаеть Copy-Past из своих наработок. А может просто свалишь с этого сайта, как многие другие. Как тебе такое развитие событий? Мне кажется замечательное...

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



Страницы: 1 вся ветка

Форум: "Потрепаться";
Текущий архив: 2004.05.02;
Скачать: [xml.tar.bz2];

Наверх





Память: 0.65 MB
Время: 0.032 c
14-1081332994
}|{yk
2004-04-07 14:16
2004.05.02
Библиотека JVCL


6-1078418515
kas-t
2004-03-04 19:41
2004.05.02
Как синхронизировать время с сервером точного времени в Интернет?


7-1078826887
Dimedrol
2004-03-09 13:08
2004.05.02
Как заблокировать клавишу Esc ?


14-1081694979
Piter
2004-04-11 18:49
2004.05.02
Quake2 написана под .NET? :)


7-1078480227
Вадим
2004-03-05 12:50
2004.05.02
"Мой компьютер"





Afrikaans Albanian Arabic Armenian Azerbaijani Basque Belarusian Bulgarian Catalan Chinese (Simplified) Chinese (Traditional) Croatian Czech Danish Dutch English Estonian Filipino Finnish French
Galician Georgian German Greek Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Korean Latvian Lithuanian Macedonian Malay Maltese Norwegian
Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish Bengali Bosnian
Cebuano Esperanto Gujarati Hausa Hmong Igbo Javanese Kannada Khmer Lao Latin Maori Marathi Mongolian Nepali Punjabi Somali Tamil Telugu Yoruba
Zulu
Английский Французский Немецкий Итальянский Португальский Русский Испанский