УКАЗАТЕЛИ И ДИНАМИЧЕСКАЯ ПАМЯТЬ
7.4.1. Динамическая память Динамическая память - это оперативная память ПК, предоставляемая программе при ее работе. Динамическое размещение данных означает использование динамической памяти непосредственно при работе программы. В отличие от этого статическое размещение осуществляется компилятором Object Pascal в процессе компиляции программы. При динамическом размещении заранее не известны ни тип, ни количество размещаемых данных. 7.4.2. Указатели Оперативная память ПК представляет собой совокупность ячеек для хранения информации - байтов, каждый из которых имеет собственный номер. Эти номера называются адресами, они позволяют обращаться, к любому байту памяти. 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; и тем самым достичь нужного результата. 7.4.3. Выделение и освобождение динамической памяти Вся динамическая память в 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.4.4. Процедуры и функции для работы с динамической памятью В табл. 7.14 приводится описание как уже рассмотренных процедур и функций Object Pascal, так и некоторых других, которые могут оказаться полезными при обращении к динамической памяти. Таблица 7.14. Средства Object Pascal для работы с памятью
Windows имеет собственные средства работы с памятью. В табл. 7.15 перечислены соответствующие API-функции и даны краткие пояснения. За более полной информацией обращайтесь к справочной службе в файлах WIN32. hlp или WIN32S. hlp. Таблица 7.15. Средства Windows для работы с памятью
|