Нуль-терминальные строки


Нуль-терминальные строки широко используются при обращениях к так называемым API-функциям Windows (API - Application Program Interface - интерфейс прикладных программ). Поскольку компоненты Delphi берут на себя все проблемы связи с API-функциями Windows, программисту редко приходится прибегать к нуль-терминальным строкам. Тем не менее в этом разделе описываются особенности обработки таких строк.
Прежде всего напомню, что базовый тип string хранит в памяти терминальный нуль, поэтому Object Pascal допускает смешение обоих типов в одном строковом выражении, а также реализует взаимное приведение типов с помощью автофункций преобразования String и PChar. Например:
procedure TfmExample.FormActivate(Sender: TObject);
var
pcS: PChar;
ssS: String;
begin
pcS := '123456';
ssS := 'X = ';
IbOutput.Caption := ssS + pcS;
end;
В строке IbOutput будет выведено х = 123456. Другой пример. В состав API-функцией входят функция MessageBox, с помощью которой на экране создается диалоговое окно с заголовком, текстовым сообщением и набором кнопок. Если в конце предыдущего примера добавить оператор
MessageBox(0, ssS + pcS, 'Заголовок окна', mb_0k);
то компилятор укажет на ошибку, т. к. вторым параметром обращения к функции должно быть выражение типа PChar, в то время как выражение sss+pcs приводится компилятором к общему типу String. Правильным будет такое обращение:
MessageBox(0, PChar (ssS + pcS), 'Заголовок окна', mb_0k) ;
Текстовые константы совместимы с любым строковым типом, поэтому третий параметр обращения (он тоже должен быть типа PChar) компилятор обработает без ошибок.
В Delphi считается совместимым с pchar и string массив символов с нулевой нижней границей. В отличие от pchar и String такой массив распределяется статически (на этапе компиляции), поэтому наполнение массива символами и завершающим нулем осуществляется специальной процедурой Strcopy:
procedure TfmExample.bbRunClick(Sender:TObject);
var
acS: array [0..6] of Char;
begin
StrCopy(acS, '123456');
IbOutput.Caption := acS;
end;
Для работы с типом pchar используются такие же операции, как и с типом String: операция конкатенации “+” и операции сравнения >, >=, <, <=, =, <>.
Таблица 7.13. Подпрограммы для работы с нуль-терминальными строками


Function CharToOem
(Str, OemStr: PChar):
Bool;

Преобразует символы строки Str из кодировки ANSI в кодировку MS-DOS и помещает результат в OemStr.Всегда возвращает True

Function CharToOemBuff(Str, OemStr: PChar; MaxLen: Lorigint): Bool;

Преобразует не более MaxLen символов строки Str из кодировки ANSI в кодировку MS-DOS и помещает результат в OemStr. Всегда возвращает True

Function OemToChar (OEMStr, Str: PChar): Bool;

Преобразует символы из кодировки MS-DOS в кодировку ANSI и возвращает True

Function OemToCharBuff(OEMStr, Str: PChar;MaxLen: Longint): Bool;

Преобразует не более MaxLen символов строки OemStr из кодировки MS-DOS в кодировку ANSI и помещает результат в Str. Всегда возвращает True

Function StrCat(Dest,Source: PChar): PChar;

Копирует строку Source в конец строки Dest и возвращает указатель на строку Dest

Function StrComp (Strl,Str2: PChar): Integers;

Побайтно сравнивает строку Strl со строкой Str2 и возвращает следующий результат: =0 для Strl=Str2; >0 для Strl>Str2,- 0 для Strl<Str2

Function StrCopy(Dest,Source: PChar): PChar;

Копирует строку Source в строку Dest и возвращает указатель на Dest. StrCopy не проверяет реальный размер памяти, связанный с Dest (он должен быть не меньше StrLen(Source)+1)

Procedure StrDispose(Str: PChar) ; 

Удаляет строку Str из памяти. Строка должна быть предварительно помещена в память функцией StrNew. Если Str=NlL, процедура ничего не делает

Function StrECopy(Dest, Source: PChar): PChar; 

Объединяет строки. Эта функция работает в точности, как StrCat, но возвращает указатель на конец сцепленных строк, т. е. на терминальный ноль

Function StrEnd(Str:
PChar): PChar;

Функция возвращает указатель на терминальный нольстроки Str

Function StrIComp(Strl,Str2: PChar): PChar; 
 

Функция сравнивает строки, игнорируя возможную разницу в высоте букв. Возвращает такой же результат, как и StrComp. Замечу, что функция правильно работает лишь с латиницей. Для кириллицы ее нужно модифици ровать (см.ниже)

Function StrLCat(Dest,Source: PChar; MaxLen:Word): PChar;
 
 

Копирует символы строки Source в конец строки Dest до тех пор, пока не будут скопированы все символы или пока длина сцепленной строки Dest не достигнет MaxLen. Возвращает указатель на сцепленную строку. В отличие от StrCopy эта функция блокирует возможноепереполнение области памяти, связанной с Dest. Обычно в качестве MaxLen используется выражение SizeOf(Dest)-!

Function StrLComp(Dest,
Source: PChar; MaxLen:
Word): PChar;

В отличие от StrComp сравнивает не более MaxLen символов строк. Возвращаемый результат такой же, как и у StrComp

Function StrLCopy(Dest,
Source: PChar; MaxLen:
Word): PChar; 

Копирует символы из строки Source в строку Dest до тех пор, пока не будет скопирована вся строка или пока не будет скопировано MaxLen символов. В отличие от StrCopy блокирует возможное переполнение области памяти, связанной с Dest. В качестве MaxLen обычно используется выражение SizeOf(Dest)-1

Function StrLen(Str:PChar): Cardinal;

Возвращает длину строки 
 

Function StrLIComp(Strl, Str2: PChar; MaxLen: Word): PChar; 

Сравнивает не более MaxLen символов строк, проверяя точное соответствие высоты букв. Возвращаемый результат см. StrComp. Функция правильно работает только с латиницей

Function StrLower(Str:
PChar): PChar;

Преобразует заглавные буквы строки Str к строчным и возвращает указатель на результат. Функция правильно работает только с латиницей

Function S t rMove(Dest,
Source: PChar; Count:Word): PChar;

Копирует точно Count символов строки Source в строку Dest и возвращает указатель на результат. Функция игнорирует действительные размеры строк и может выйти за их пределы

Function StrNew(Str:PChar): PChar;

Помещает строку в память

Function StrPas(Str:PChar): String;

Преобразует нуль-терминальную строку в строку String

Function StrPCopytStr: PChar; S: String):PChar;

Преобразует строку String в нуль-терминальную строку. Возвращает указатель на Str

Function StrPos(Strl,
Str2: PChar): PChar;

Ищет подстроку Str2 в строке Strl и возвращает указатель на первое вхождение Str2 или MIL, если подстрока не найдена

Function StrRScan(Str:
PChar; Ch: Char):PChar;

Ищет символ Ch в строке Str и возвращает указатель напоследний обнаруженный символ Ch или NIL, если символ не найден

Function StrScan(Str:PChar; Ch: Char):PChar;

Ищет символ Ch в строке Str и возвращает указатель на первый обнаруженный символ Ch или MIL, если символ не найден

Function StrUpper (Str: PChar) : PChar

Преобразует строчные буквы строки Str к заглавным и возвращает указатель на результат. Функция правильно работает только с латиницей

Функции преобразования из ANSI-кодировки в кодировку MS-DOS (charToxxx) и обратно (OеmTоххх) правильно работают с кириллицей, если в MS-DOS используется национальная страница 866 (так называемая альтернативная кодировка). А вот четыре функции, использующие преобразование высоты букв (strLower, StrUpper, Stricomp и StrLIComp), работают корректно только для букв латинского алфавита (латиницы). Для русских букв вместо обращения к этим функциям следует использовать стандартные функции AnsiLowerCase И AnsiUpperCase, которые используют как параметры String, так и PChar, но возвращают результат типа string:
var
acS: array [Byte] of Char;
begin
StrCopy(acS, 'заглавные буквы');
Caption := AnsiUpperCase(acS) end;
Аналогично для функции Stricomp:
var
apSl,apS2: array [0..1000] of Char;
begin
StrCopy(apSl,'эталон');
StrCopy(apS2,'ЭТАЛОН') ;
Caption := IntToStr(StrIComp(PChar(AnsiUpperCase(apSl)),
PChar(AnsiUpperCase(apS2))))
end;
УКАЗАТЕЛИ И ДИНАМИЧЕСКАЯ ПАМЯТЬ
Динамическая память
Динамическая память - это оперативная память ПК, предоставляемая программе при ее работе. Динамическое размещение данных означает использование динамической памяти непосредственно при работе программы. В отличие от этого статическое размещение осуществляется компилятором Object Pascal в процессе компиляции программы. При динамическом размещении заранее не известны ни тип, ни количество размещаемых данных.

 

Указатели


Оперативная память ПК представляет собой совокупность ячеек для хранения информации - байтов, каждый из которых имеет собственный номер. Эти номера называются адресами, они позволяют обращаться, к любому байту памяти. Object Pascal предоставляет в распоряжение программиста гибкое средство управления динамической памятью - так называемые указатели. Указатель - это переменная, которая в качестве своего значения содержит адрес байта памяти. С помощью указателей можно размещать в динамической памяти любой из известных в Object Pascal типов данных. Лишь некоторые из них (Byte, Char, ShortInt, Boolean) занимают во внутреннем представлении один байт, остальные - несколько смежных. Поэтому на самом деле указатель адресует лишь первый байт данных.
Как правило, указатель связывается с некоторым типом данных. Такие указатели будем называть типизированными. Для объявления типизированного указателя используется значок ^, который помещается перед соответствующим типом, например:
var
p1 : ^Integer;
р2 : ^Real;
type
PerconPointer = "PerconRecord;
PerconRecord = record Name : String;
Job : String;
Next : PerconPointer ,
end;
Обратите внимание: при объявлении типа PerconPointer мы сослались на тип PerconRecord, который предварительно в программе объявлен не был. Как уже отмечалось, в Object Pascal последовательно проводится в жизнь принцип, в соответствии с которым перед использованием какого-либо идентификатора он должен быть описан. Исключение сделано только для указателей, которые могут ссылаться на еще не объявленный тип данных.
В Object Pascal можно объявлять указатель и не связывать его при этом с каким-либо конкретным типом данных. Для этого служит стандартный тип pointer, например:
var
р: Pointer;
Указатели такого рода будем называть нетипизированньти. Поскольку нетипизированные указатели не связаны с конкретным типом, с их помощью удобно динамически размещать данные, структура и тип которых меняются в ходе работы программы.
Как уже говорилось, значениями указателей являются адреса переменных в памяти, поэтому следовало бы ожидать, что значение одного указателя можно передавать другому. На самом деле это не совсем так. В Object Pascal можно передавать значения только между указателями, связанными с одним и тем же типом данных.
Если, например,
var
pI1,pI2: ^integer;
pR: ^Real;
p: Pointer;
то присваивание
pI1 := pI2;
вполне допустимо, в то время как
pl1 :=pR;
запрещено, поскольку pI1 и pR указывают на разные типы данных. Это ограничение, однако, не распространяется на нетипизированные указатели, поэтому мы могли бы записать
p := pR;
pI1 := p;
и тем самым достичь нужного результата.
Выделение и освобождение динамической памяти
Вся динамическая память в Object Pascal рассматривается как сплошной массив байтов, который называется кучей.
Память под любую динамически размещаемую переменную выделяется процедурой New. Параметром обращения к этой процедуре является типизированный указатель. В результате обращения указатель приобретает значение, соответствующее адресу, начиная с которого можно разместить данные, например:
var pI,pJ: ^Integer;
pR: ^Real;
begin
New (pI) ;
New (pR) ;
end;
После того как указатель приобрел некоторое значение, т. е. стал указывать на конкретный физический байт памяти, по этому адресу можно разместить любое значение соответствующего типа. Для этого в операторе присваивания сразу за указателем без каких-либо пробелов ставится значок ^ , например:
pJ^ := 2; // В область памяти pJ помещено значение 2
pl^ := 2*pi; // В область памяти pR помещено значение 6.28
Таким образом, значение, на которое указывает указатель, т. е. собственно данные, размещенные в куче, обозначаются значком ^, который ставится сразу за указателем. Если за указателем нет значка ^ , то имеется в виду адрес, по которому размещены данные. Имеет смысл еще раз задуматься над только что сказанным: значением любого указателя является адрес, а чтобы указать, что речь идет не об адресе, а о тех данных, которые размещены по этому адресу, за указателем ставится ^ (иногда об этом говорят как о разыменовании указателя).
Динамически размещенные данные можно использовать в любом месте программы, где это допустимо для констант и переменных соответствующего типа, например:
рR^ := Sqr(pR") + I^ - 17;
Разумеется, совершенно недопустим оператор
pR := Sqr(pR") + I^ - 17;
так как указателю pR нельзя присвоить значение вещественного выражения. Точно так же недопустим оператор
pR ^ := Sqr(pR) ;
поскольку значением указателя pR является адрес и его (в отличие от того значения, которое размещено по этому адресу) нельзя возводить в квадрат. Ошибочным будет и такое присваивание:
pR^' := pJ;
так как вещественным данным, на которые указывает pR, нельзя присвоить значение указателя (адрес).
Динамическую память можно не только забирать из кучи, но и возвращать обратно. Для этого используется процедура Dispose. Например, операторы
Dispose(pJ);
Dispose(pR);
вернут в кучу память, которая ранее была закреплена за указателями pJ и pR (см. выше).
Замечу, что процедура Dispose (pPtr) не изменяет значения указателя pPtr, а лишь возвращает в кучу память, ранее связанную с этим указателем. Однако повторное применение процедуры к свободному указателю приведет к возникновению ошибки периода исполнения. Освободившийся указатель программист может пометить зарезервированным словом nil. Помечен ли какой-либо указатель или нет, можно проверить следующим образом:
const
pR: ^Real = NIL;
begin
if pR = NIL then
New (pR) ;
Dispose(pR) ;
pR := NIL;
end;
Никакие другие операции сравнения над указателями не разрешены.
Приведенный выше фрагмент иллюстрирует предпочтительный способ объявления указателя в виде типизированной константы с одновременным присвоением ему значения nil. Следует учесть, что начальное значение указателя (при его объявлении в разделе переменных) может быть произвольным. Использование указателей, которым не присвоено значение процедурой New или другим способом, не контролируется Delphi и вызовет исключение.
Как уже отмечалось, параметром процедуры New может быть только типизированный указатель. Для работы с нетипизированными указателями используются Процедуры GetMem И FreeMem:
GetMem(P, Size); // резервирование памяти;
FreeMem(P, Size); // освобождение памяти.
Здесь р - нетипизированный указатель; size - размер в байтах требуемой или освобождаемой части кучи.
Примечание
Испoльзoвaние прцeдyp GetMem/FreeMemMem, как и вообще вся работа диамияесжой памятью, требует особой осторожности и тщателвного солюдения простого правила: освобождать нужно ровно столько пайти, сколько её было зарезервировано, и именно с того адреса, с которого она была зарезёрвирована.
Процедуры и функции для работы с динамической памятью
В табл. 7.14 приводится описание как уже рассмотренных процедур и функций Object Pascal, так и некоторых других, которые могут оказаться полезными при обращении к динамической памяти.
Таблица 7.14. Средства Object Pascal для работы с памятью


Function Addr(X):
Pointer;

Возвращает адрес аргумента X. Аналогичный результат возвращает операция @

Procedure Dispose (var P: Pointer) ;

Возвращает в кучу фрагмент динамической памяти, который ранее был зарезервирован за типизированным указателем P

Procedure Free-Mem(var P: Pointer; Size: Integer) ;

Возвращает в кучу фрагмент динамической памяти, который ранее был зарезервирован за нетипизированным указателем Р 
 

Procedure Get-Mem(var P: Pointer; Size: Integer) ;

Резервирует за нетипизированным указателем Р фрагментдинамической памяти требуемого размера Size  

Procedure New(var P: Pointer) ;

Резервирует фрагмент кучи для размещения переменной и помещает в типизированный указатель Р адрес первого байта

Function SizeOf(X): Integer;

Возвращает длину в байтах внутреннего представления указанного объекта Х

 
Windows имеет собственные средства работы с памятью. В табл. 7.15 перечислены соответствующие API-функции и даны краткие пояснения. За более полной информацией обращайтесь к справочной службе в файлах WIN32. hlp или WIN32S. hlp.
Таблица 7.15. Средства Windows для работы с памятью


CopyMemory
 

Копирует содержимое одного блока памяти в другой блок.
Блоки не должны перекрываться хотя бы частично

FillMemory

Заполняет блок памяти указанным значением

GetProcessHeap

Возвращает дескриптор кучи для текущей программы

GetProcessHeaps

Возвращает дескрипторы куч для всех работающих программ

GlobalAlloc

Резервирует в куче блок памяти требуемого размера

GlobalDiscard

Выгружает блок памяти

GlobalFlags

Возвращает информацию об указанном блоке памяти

GlobalFree 

Освобождает блок памяти и возвращает его в общий пул памяти

GlobalHandle 

Возвращает дескриптор блока памяти, связанного с заданным указателем

GlobalLock

Фиксирует блок памяти и возвращает указатель на его первый байт

GlobalMemoryStatus
 

Возвращает информацию о доступной памяти (как физической, так и виртуальной)

GlobalReAlloc  

Изменяет размер и атрибуты ранее зарезервированного блока памяти

GlobalSize

Возвращает размер в байтах блока памяти

GlobalUnlock

Снимает фиксацию блока памяти и делает его перемещаемым

HeapAlloc

Резервирует в куче неперемещаемый блок памяти

HeapCompact

Удаляет фрагментацию кучи

HeapCreate

Создает для программы новую кучу

HeapDestroy

Возвращает кучу в общий пул памяти

HeapFree
 

Освобождает блок памяти, зарезервированный функциями
HeapAlloc или HeapReAlloc

HeapLock

Делает указанную кучу доступной только для текущего потока

HeapReAlloc

Изменяет размер и/или свойства кучи

HeapSize

Возвращает размер кучи в байтах

HeapUnlock 

Делает указанную кучу доступной для любых потоков текущего процесса

HeapValidate

Проверяет состояние кучи или размещенного в ней блока памяти

IsBadCodePtr 
 

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

IsBadHugeReadPtr
 

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

IsBadHugeWritePtr 

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

IsBadReadPtr

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

IsBadStringPtr

Сообщает, может ли программа читать содержимое строки, распределенной в куче

IsBadWritePtr

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

LocalAlloc

Аналогична GlobalAlloc

:: LocalDiscard

Аналогична GloalDiscard

'LocalFlags

Аналогична GlobalFlags

LocalFree

Аналогична Global Free

LocalHandle

Аналогична GlobalHandle

LocalLock

Аналогична GlobalLock

LocalReAlloc

Аналогична GlobalReAlloc

LocalSize

Аналогична GlobalSize

LocalUnlock

Аналогична GlobalUnlock

MoveMemory 

Копирует один блок памяти в другой. Блоки могут перекрываться

VirtualAlloc

Резервирует блок виртуальной памяти

VirtualFree

Освобождает блок виртуальной памяти

VirtualLock

Фиксирует блок виртуальной памяти

VirtualProtect 

Изменяет права доступа текущей программы к виртуальному блоку памяти

VirtualProtectEx

Изменяет права доступа указанной программы к виртуальному блоку памяти

VirtualQuery 

Возвращает свойства виртуального блока памяти по отношению к вызывающей программе

VirtualQueryEx

Возвращает свойства виртуального блока памяти по отношению к указанной программе

VirtualUnloc'k

Снимает фиксацию блока виртуальной памяти

ZeroMemory

Заполняет блок памяти нулями

 
ПСЕВДОНИМЫ ТИПОВ
Для любого типа можно объявить сколько угодно псевдонимов. Например:
type
TMyInteger = Integer;
В дальнейшем псевдоним можно использовать так же, как и базовый тип:
var
Mylnt: TMyInteger;
begin
Mylnt := 2*Round(pi);
end;
Такого рода псевдонимы обычно используются для повышения наглядности кода программы. Однако в Object Pascal можно объявлять строго типизированные псевдонимы добавлением зарезервированного слова type перед именем базового типа:
type
TMyIntegerType = type Integer;
var
MylntVar: TMyIntegerType;
С точки зрения компилятора, типизированные псевдонимы совместимы с базовым типом в различного рода выражениях, но фактически они объявляют новый тип данных, поэтому их нельзя использовать в качестве формальных параметров обращения к подпрограммам вместо базового типа. Если, например, объявлена процедура
function MylntFunc(APar: integer): Integer;
begin
end;
то такое обращение к ней
MylntFunc(MylntVar)
будет расценено компилятором как ошибочное.
Строго типизированные псевдонимы заставляют компилятор вырабатывать информацию о типе для этапа прогона программы (RTTI - Run-Time Type Information). Эта информация обычно используется средой Delphi для обеспечения функционирования разного рода редакторов свойств и программ-экспертов.

 

 
На главную | Содержание | < Назад....Вперёд >
С вопросами и предложениями можно обращаться по nicivas@bk.ru. 2013 г. Яндекс.Метрика