FreeMem — Процедура Delphi


Содержание

FreeMem — Процедура Delphi

Основные процедуры для работы с динамическими переменными приведены ниже.

Dispose Высвобождает память из-под динамической переменной.
Finalize Деинициализирует динамическую переменную.
FreeMem Высвобождает память из-под динамической переменной.
GetMem Создает динамическую переменную, выделяя под нее указанный объем памяти.
Initialize Инициализирует динамическую переменную.
New Создает динамическую переменную.
ReallocMem Перераспределяет память для динамической переменной.
Процедура Dispose( var P: Pointer);

Описание:
Процедура высвобождает область памяти, которую использует динамическая переменная P. Значение указателя P в данном случае становится неопределенным. Если функции передан недопустимый указатель, то возникает исключение EInvalidPointer. Обработка ошибок с помощью механизма обработки исключительных ситуаций включается директивой компилятора <$I+>.

Пример: Процедура Finalize( var V [; Count: Integer] );

Описание:
Процедура деинициализирует динамическую переменную, указанную в параметре V. Данная процедура должна использоваться только в тех случаях, когда для высвобождения памяти из-под динамической переменной не используется процедура Dispose. Для объектов глобальных, локальных и динамических переменных при высвобождении памяти с помощью стандартной процедуры Dispose, компилятор генерирует код, завершающий работу с длинными строками, переменными типа Variant и интерфейсами после разрушения переменной. Если память, содержащая не пустые или не инициализированные длинные строки, Variant-переменные или интерфейсы, высвобождается не при помощи процедуры Dispose (например, процедурой FreeMem), то перед высвобождением памяти требуется вызвать процедуру Finalize для того, чтобы закрыть указанную переменную. Процедура Finalize присваивает всем длинным строкам пустое значение, а переменные типа Variant и интерфейсы деинициализирует (устанавливает тип Unassigned). Дополнительный параметр Count может быть определен в тех случаях, когда необходимо высвободить память, из-под нескольких переменных, содержащихся в непрерывном блоке памяти (например, динамически распределенный массив строк) для того, чтобы закрыть все переменные одной операцией. Если переменная, определенная в параметре V не содержит длинных строк, Variant-значений и интерфейсов, то компилятор просто игнорирует вызов процедуры.

Пример:
См. пример к функции FreeMem.

Процедура FreeMem( var P: Pointer [; Size: Integer] );

Описание:
Процедура уничтожает переменную, с которой связан указатель P и высвобождает память, занимаемую данной переменной. В необязательном параметре Size указывается объем памяти в байтах, выделенный ранее динамически под переменную. Если после действия процедуры FreeMem, вызвать указатель P, то возникнет ошибка, т.к. указатель имеет неопределенное значение.

Пример: Процедура GetMem( var P: Pointer; Size: Integer );

Описание:
Процедура создает динамическую переменную: выделяет блок памяти размером Size байт под переменную, указанную в параметре P, и возвращает указатель на начало данного блока памяти. Параметр P может представлять собой любой тип указателя. Указатель на новую созданную переменную записывается как P^. Если для создания динамической переменной недостаточно памяти, то возникает исключение EOutOfMemory.

Пример:
См. пример к функции FreeMem.

Процедура Initialize( var V [ ; Count: Integer] );

Описание:
Процедура инициализирует динамическую переменную. Если динамическая переменная была создана не с помощью процедуры New, а другим способом (например, с помощью процедуры GetMem или процедуры ReallocMem), то после создания переменной, ее необходимо инициализировать процедурой Initialize. При вызове данная процедура обнуляет память, занятую длинными строками Variant-значениями и интерфейсами. Длинным строкам присваивается пустое значение, а для Variant-значений и интерфейсов устанавливается неопределенный тип (Unassigned). Необязательный параметр Count может быть определен, когда память под несколько переменных выделена в непрерывном адресном пространстве. Это позволяет инициализировать все переменные одним вызовом процедуры. Если переменная, определенная в параметре V не содержит длинных строк, Variant-значений и интерфейсов, то компилятор игнорирует данный вызов процедуры и не генерирует ни какого кода.

Процедура New( var P: Pointer );

Описание:
Процедура создает новую динамическую переменную, и ассоциирует с ней указатель P. Параметр P может представлять собой любой тип указателей. Размер памяти, выделяемый под переменную, зависит от типа указателя. Новая созданная переменная может быть вызвана как P^. Если для создания динамической переменной недостаточно памяти, то возникает исключение EOutOfMemory. По завершению использования динамической переменной память, выделенную ранее процедурой New, необходимо высвободить вызовом процедуры Dispose.

Пример:
См. пример к функции Dispose.

Процедура ReallocMem( var P: Pointer; Size: Integer );

Описание:
Процедура перераспределяет память размером Size байт под динамическую переменную P. При вызове данной процедуры указатель P должен иметь значение nil или должен указывать на динамическую переменную, память под которую была предварительно выделена с помощью процедур GetMem или ReallocMem.Если P = nil, Size = 0, то процедура не производит никаких действий.Если P = nil, а Size <> 0, то процедура распределяет новый блок памяти размером Size и устанавливает указатель P на начало блока. Такой вызов процедуры аналогичен обращению к процедуре GetMem. Если P <> nil, а Size = 0, то процедура высвобождает блок памяти, на который указывает P и устанавливает P = nil. Вызов процедуры с указанными параметрами аналогичен обращению к процедуре FreeMem, но в отличие от FreeMem процедура ReallocMem очищает указатель.

Процедуры работы с динамической памятью

Procedure Dispose(var P: Pointer);

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

После вызова Dispose значение P не определено. При включенной директиве <$I+>, вы можете использовать исключительные ситуации, чтобы обработать эту ошибку.

Procedure FreeMem(var P: Pointer [; Size: Integer]);

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

P — переменная любого типа-указателя, предварительно созданная процедурой GetMem.

Size определяет размер динамической переменной в байтах. Для корректной работы этой процедуры, Size должно быть точно равно числу байтов, предварительно распределенных для этой переменной процедурой GetMem.

FreeMem уничтожает переменную P и возвращает память «куче». Если P не указывает на память в «куче», возникает ошибка времени выполнения.

После вызова FreeMem, значение P не определено, и происходит ошибка, если Вы впоследствии ссылаетесь на P^. Вы можете использовать исключительные ситуации, чтобы обработать эту ошибку.

Procedure GetMem(var P: Pointer; Size: Integer);

GetMem создает динамическую переменную определенного размера и помещает адрес блока в переменную Р.

P — переменная любого типа-указателя. Size — выражение, определяющее размер в байтах динамической переменной. Вы должны ссылаться на созданную переменную как P^.

Procedure New(var P: Pointer);

Создает новую динамическую переменную и помещает ее адрес в переменную Р. P — переменная любого типа-указателя. Размер распределенного блока памяти равен размеру типа, на который указывает P. Если памяти недостаточно, чтобы распределить динамическую переменную, возникает исключительная ситуация EOutOfMemory.

При завершении программы, все динамические переменные, созданные процедурами New или GetMem, должны быть уничтожены соответственно процедурами Dispose или FreeMem.

FreeMem — Процедура Delphi

Для динамического распределения памяти служат еще две тесно взаимосвязанные процедуры: GetMem и FreeMem. Подобно New и Dispose, они во время вызова выделяют и освобождают память для одной динамической переменной:

GetMem(var P: Pointer; Size: Integer) — создает в динамической памяти новую динамическую переменную c заданным размером Size и присваивает ее адрес указателю P. Переменная-указатель P может указывать на данные любого типа.

FreeMem(var P: Pointer [; Size: Integer] ) — освобождает динамическую переменную.

Если в программе используется этот способ распределения памяти, то вызовы GetMem и FreeMem должны соответствовать друг другу. Обращения к GetMem и FreeMem могут полностью соответствовать вызовам New и Dispose.

New(P4); // Выделить блок памяти для указателя P4
.
Dispose(P4); // Освободить блок памяти

Следующий отрывок программы даст тот же самый результат:
GetMem(P4, SizeOf(ShortString)); // Выделить блок памяти для P4
.
FreeMem(P4); // Освободить блок памяти

С помощью процедуры GetMem одной переменной-указателю можно выделить разное количество памяти в зависимости от потребностей. В этом состоит ее основное отличие от процедуры New.

GetMem(P4, 20); // Выделить блок в 20 байт для указателя P4
.
FreeMem(P4); // Освободить блок памяти

В данном случае для указателя P4 выделяется меньше памяти, чем может уместиться в переменной типа ShortString, и программист сам должен обеспечить невыход строки за пределы выделенного участка.

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

ReallocMem(var P: Pointer; Size: Integer) — освобождает блок памяти по значению указателя P и выделяет для указателя новый блок памяти заданного размера Size. Указатель P может иметь значение nil, а параметр Size — значение 0, что влияет на работу процедуры:

если P = nil и Size = 0, процедура ничего не делает;

если P = nil и Size <> 0, процедура выделяет новый блок памяти заданного размера, что соответствует вызову процедуры GetMem.

если P <> nil и Size = 0, процедура освобождает блок памяти, адресуемый указателем P и устанавливает указатель в значение nil. Это соответствует вызову процедуры FreeMem, с той лишь разницей, что FreeMem не очищает указатель;

если P <> nil и Size <> 0, процедура перевыделяет память для указателя P. Размер нового блока определяется значением Size. Данные из прежнего блока копируются в новый блок. Если новый блок больше прежнего, то приращенный участок остается неинициализированным и содержит случайные данные.

Программирование на языке Delphi
Глава 2. Основы языка Delphi. Часть 4

Файлы

Понятие файла

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

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

В зависимости от типа элементов различают три вида файла:

  • файл из элементов фиксированного размера; элементами такого файла чаще всего являются записи;
  • файл из элементов переменного размера (нетипизированный файл); такой файл рассматривается просто как последовательность байтов;
  • текстовый файл; элементами такого файла являются текстовые строки.

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

Для работы с файлом, состоящим из типовых элементов переменная объявляется с помощью словосочетания file of, после которого записывается тип элемента:

К моменту такого объявления тип TPerson должен быть уже описан (см. выше).

Объявление переменной для работы с нетипизированным файлом выполняется с помощью отдельного слова file:

Для работы с текстовым файлом переменная описывается с типом TextFile:

Работа с файлами

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

Приступая к работе с файлом, нужно первым делом вызвать процедуру AssignFile, чтобы файловой переменной поставить в соответствие имя файла на диске:

В результате этого действия поля файловой переменной F инициализируются начальными значениями. При этом в поле имени файла заносится строка ‘MyFile.txt’.

Так как файла еще нет на диске, его нужно создать:

Теперь запишем в файл несколько строк текста. Это делается с помощью хорошо вам знакомых процедур Write и Writeln:

При работе с файлами первый параметр этих процедур показывает, куда происходит вывод данных.

После работы файл должен быть закрыт:

Рассмотрим теперь, как прочитать содержимое текстового файла. После инициализации файловой переменной (AssignFile) файл открывается с помощью процедуры Reset:

Для чтения элементов используются процедуры Read и Readln, в которых первый параметр показывает, откуда происходит ввод данных. После работы файл закрывается. В качестве примера приведем программу, распечатывающую в своем окне содержимое текстового файла ‘MyFile.txt’:

Так как обычно размер файла заранее не известен, перед каждой операцией чтения вызывается функция Eof, которая возвращает True, если достигнут конец файла.

Внимание! Текстовые файлы можно открывать только для записи или только для чтения, но не для того и другого одновременно. Для того чтобы сначала записать текстовый файл, а потом прочитать, его нужно закрыть после записи и снова открыть, но уже только для чтения.

Стандартные подпрограммы управления файлами

Для обработки файлов в языке Delphi имеется специальный набор процедур и функций:

  • AssignFile(var F; FileName: string) — связывает файловую переменную F и файл, имя которого указано в FileName.
  • Reset(var F [: File; RecSize: Word ] ) — открывает существующий файл. Если открывается нетипизированный файл, то RecSize задает размер элемента файла.
  • Rewrite(var F [: File; RecSize: Word ] ) — создает и открывает новый файл.
  • Append(var F: TextFile) — открывает текстовый файл для добавления текста.
  • Read(F, V1 [, V2, . Vn ]) — начиная с текущей позиции, читает из типизированного файла подряд расположенные элементы в переменные V1, V2, . Vn.
  • Read(var F: TextFile; V1 [, V2, . Vn ] ) — начиная с текущей позиции, читает из текстового файла символы или строки в переменные V1, V2, . Vn.
  • Write(F, V1 [, V2, . Vn ]) — начиная с текущей позиции, записывает в типизированный файл значения V1, V2, . Vn.
  • Write(var F: TextFile; V1 [, V2, . Vn ] ) — начиная с текущей позиции указателя чтения-записи, записывает в текстовый файл значения V1, V2, . Vn.
  • CloseFile(var F) — закрывает ранее открытый файл.
  • Rename(var F; NewName: string) — переименовывает неоткрытый файл F любого типа. Новое имя задается в NewName.
  • Erase(var F) — удаляет неоткрытый внешний файл любого типа, заданный переменной F.
  • Seek(var F; NumRec: Longint) — устанавливает позицию чтения-записи на элемент с номером NumRec; F — типизированный или нетипизированный файл.
  • SetTextBuf(var F: TextFile; var Buf [; Size: Word]) — назначает текстовому файлу F новый буфер ввода-вывода Buf объема Size.
  • SetLineBreakStyle(var T: Text; Style: TTextLineBreakStyle) — устанавливает способ переноса строк в файле (одиночный символ #10 или пара символов #13#10).
  • Flush(var F: TextFile) — записывает во внешний файл все символы, переданные в буфер для записи.
  • Truncate(var F) — урезает файл, уничтожая все его элементы, начиная с текущей позиции.
  • IOResult: Integer — возвращает код, характеризующий результат (была ошибка или нет) последней операции ввода-вывода.
  • FilePos(var F): Longint — возвращает для файла F текущую файловую позицию (номер элемента, на которую она установлена, считая от нуля). Не используется с текстовыми файлами.
  • FileSize(var F): Longint — возвращает число компонент в файле F. Не используется с текстовыми файлами.
  • Eoln(var F: Text): Boolean — возвращает булевское значение True, если текущая позиция чтения-записи находится на маркере конца строки. Если параметр F не указан, функция применяется к стандартному устройству ввода с именем Input.
  • Eof(var F): Boolean — возвращает булевское значение True, если текущая позиция чтения-записи находится сразу за последним элементом, и False в противном случае.
  • SeekEoln(var F: Text): Boolean — возвращает True при достижении маркера конца строки. Все пробелы и знаки табуляции, предшествующие маркеру, пропускаются.
  • SeekEof(var F: Text): Boolean — возвращает значение True при достижении маркера конца файла. Все пробелы и знаки табуляции, предшествующие маркеру, пропускаются.

Для работы с нетипизированными файлами используются процедуры BlockRead и BlockWrite. Единица обмена

  • BlockRead(var F: File; var Buf; Count: Word [; Result: Word] ) — считывает из файла F определенное число блоков в память, начиная с первого байта переменной Buf. Параметр Buf представляет любую переменную, используемую для накопления информации из файла F. Параметр Count задает число считываемых блоков. Параметр Result является необязательным и содержит после вызова процедуры число действительно считанных записей. Использование параметра Result подсказывает, что число считанных блоков может быть меньше, чем задано параметром Count.
  • BlockWrite(var F: File; var Buf; Count: Word [; Result: Word]) — предназначена для быстрой передачи в файл F определенного числа блоков из переменной Buf. Все параметры процедуры BlockWrite аналогичны параметрам процедуры BlockRead.
  • ChDir(const S: string) — устанавливает текущий каталог.
  • CreateDir(const Dir: string): Boolean — создает новый каталог на диске.
  • MkDir(const S: string) — аналог функции CreateDir. Отличие в том, что в случае ошибки при создании каталога функция MkDir создает исключительную ситуацию.
  • DeleteFile(const FileName: string): Boolean — удаляет файл с диска.
  • DirectoryExists(const Directory: string): Boolean — проверяет, существует ли заданный каталог на диске.
  • FileAge(const FileName: string): Integer — возвращает дату и время файла в числовом системно-зависимом формате.
  • FileExists(const FileName: string): Boolean — проверяет, существует ли на диске файл с заданным именем.
  • FileIsReadOnly(const FileName: string): Boolean — проверяет, что заданный файл можно только читать.
  • FileSearch(const Name, DirList: string): string — осуществляет поиск заданого файла в указанных каталогах. Список каталогов задается параметром DirList; каталоги разделяются точкой с запятой для операционной системы Windows и запятой для операционной системы Linux. Функция возвращает полный путь к файлу.
  • FileSetReadOnly(const FileName: string; ReadOnly: Boolean): Boolean — делает файл доступным только для чтения.
  • FindFirst/FindNext/FindClose
  • ForceDirectories(Dir: string): Boolean — создает новый каталог на диске. Позволяет одним вызовом создать все каталоги пути, заданного параметром Dir.
  • GetCurrentDir: string — возвращает текущий каталог.
  • SetCurrentDir(const Dir: string): Boolean — устанавливает текущий каталог. Если это сделать невозможно, функция возвращет значение False.
  • RemoveDir(const Dir: string): Boolean — удаляет каталог с диска; каталог должен быть пустым. Если удалить каталог невозможно, функция возвращет значение False.
  • RenameFile(const OldName, NewName: string): Boolean — изменяет имя файла. Если это сделать невозможно, функция возвращет значение False.
  • ChangeFileExt(const FileName, Extension: string): string — возвращает имя файла с измененным расширением.
  • ExcludeTrailingPathDelimiter(const S: string): string — отбрасывает символ-разделитель каталогов (символ ‘/’ — для Linux и ‘\’ — для Windows), если он присутствует в конце строки.
  • IncludeTrailingPathDelimiter(const S: string): string — добавляет символ-разделитель каталогов (символ ‘/’ — для Linux и ‘\’ — для Windows), если он отсутствует в конце строки.
  • ExpandFileName(const FileName: string): string — возвращает полное имя файла (с абсолютным путем) по неполному имени.
  • ExpandUNCFileName(const FileName: string): string — возвращает полное сетевое имя файла (с абсолютным сетевым путем) по неполному имени. Для операционной системы Linux эта функция эквивалентна функции ExpandFileName.
  • ExpandFileNameCase(const FileName: string; out MatchFound: TFilenameCaseMatch): string — возвращает полное имя файла (с абсолютным путем) по неполному имени, допуская несовпадения заглавных и строчных букв в имени файла для тех файловых систем, которые этого не допускают (например, файловая система ОС Linux).
  • ExtractFileDir(const FileName: string): string — выделяет путь из полного имени файла; путь не содержит в конце символ-разделитель каталогов.
  • ExtractFilePath(const FileName: string): string — выделяет путь из полного имени файла; путь содержит в конце символ-разделитель каталогов.
  • ExtractRelativePath(const BaseName, DestName: string): string — возвращает относительный путь к файлу DestName, отсчитанный от каталога BaseName. Путь BaseName должен заканчиваться символом-разделителем каталогов.
  • ExtractFileDrive(const FileName: string): string — выделяет имя диска (или сетевого каталога) из имени файла. Для операционной системы Linux функция возвращает пустую строку.
  • ExtractFileExt(const FileName: string): string — выделяет расширение файла из его имени.
  • ExtractFileName(const FileName: string): string — выделяет имя файла, отбрасывая путь к нему.
  • IsPathDelimiter(const S: string; Index: Integer): Boolean — проверяет, является ли символ S[Index] разделителем каталогов.
  • MatchesMask(const Filename, Mask: string): Boolean — проверяет, удовлетворяет ли имя файла заданной маске.

Указатели

Понятие указателя

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

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

Переменная P занимает 4 байта и может содержать адрес любого участка памяти, указывая на байты со значениями любых типов данных: Integer, Real, string, record, array и других. Чтобы инициализировать переменную P, присвоим ей адрес переменной N. Это можно сделать двумя эквивалентными способами:

В дальнейшем мы будем использовать более краткий и удобный второй способ.


Если некоторая переменная P содержит адрес другой переменной N, то говорят, что P указывает на N. Графически это обозначается стрелкой, проведенной из P в N (рисунок 12 выполнен в предположении, что N имеет значение 10):

Рисунок 12. Графическое изображение указателя P на переменную N

Теперь мы можем изменить значение переменной N, не прибегая к идентификатору N. Для этого слева от оператора присваивания запишем не N, а P вместе с символом ^:

Символ ^, записанный после имени указателя, называется оператором доступа по адресу. В данном примере переменной, расположенной по адресу, хранящемуся в P, присваивается значение 10. Так как в переменную P мы предварительно занесли адрес N, данное присваивание приводит к такому же результату, что и

Однако в примере с указателем мы умышленно допустили одну ошибку. Дело в том, что переменная типа Pointer может содержать адреса переменных любого типа, не только Integer. Из-за сильной типизации языка Delphi перед присваиванием мы должны были бы преобразовать выражение P^ к типу Integer:

Согласитесь, такая запись не совсем удобна. Для того, чтобы сохранить простоту и избежать постоянных преобразований к типу, указатель P следует объявить так:

При такой записи переменная P по прежнему является указателем, но теперь ей можно присваивать адреса только целых переменных. В данном случае указатель P называют типизированным, в отличие от переменных типа Pointer, которые называют нетипизированными указателями. При использовании типизированных указателей лучше предварительно вводить соответствующий указательный тип данных, а переменные-указатели просто объявлять с этим типом. Поэтому предыдущий пример можно модифицировать следующим образом:

PInteger — это указательный тип данных. Чтобы отличать указательные типы данных от других типов, будем назначать им идентификаторы, начинающиеся с буквы P (от слова Pointer). Объявление указательного типа данных является единственным способом введения указателей на составные переменные, такие как массивы, записи, множества и другие. Например, объявление типа данных для создания указателя на некоторую запись TPerson может выглядеть так:

Переменная P, описанная с типом данных PPerson, является указателем и может содержать адрес любой переменной типа TPerson. Впредь все указатели мы будем вводить через соответствующие указательные типы данных. Типом Pointer будем пользоваться лишь тогда, когда это действительно необходимо или оправдано.

Динамическое распределение памяти

После объявления в секции var указатель содержит неопределенное значение. Поэтому переменные-указатели, как и обычные переменные, перед использованием нужно инициализировать. Отсутствие инициализации указателей является наиболее распространенной ошибкой среди новичков. Причем если использование обычных неинициализированных переменных приводит просто к неправильным результатам, то использование неинициализированных указателей обычно приводит к ошибке «Access violation» (доступ к неверному адресу памяти) и принудительному завершению приложения.

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

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

Она выделяет требуемый по размеру участок памяти и заносит его адрес в переменную-указатель P. В следующем примере создаются 4 динамических переменных, адреса которых присваиваются переменным-указателям P1, P2, P3 и P4:

Далее по адресам в указателях P1, P2, P3 и P4 можно записать значения:

В таком контексте динамические переменные P1^, P2^, P3^ и P4^ ничем не отличаются от обычных переменных соответствующих типов. Операции над динамическими переменными аналогичны подобным операциям над обычными переменными. Например, следующие операторы могут быть успешно откомпилированы и выполнены:

После работы с динамическими переменными необходимо освободить занимаемую ими память. Для этого предназначена процедура:

Например, в приведенной выше программе явно не хватает следующих строк:

После выполнения данных утверждений указатели P1, P2, P3 и P4 опять перестанут быть связаны с конкретными адресами памяти. В них будут случайные значения, как и до обращения к процедуре New. Не стоит делать попытки присвоить значения переменным P1^, P2^, P3^ и P4^, ибо в противном случае это может привести к нарушению нормальной работы программы.

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

Поэтому следует четко придерживаться последовательности действий при работе с динамическими переменными:

  • создать динамическую переменную;
  • выполнить с ней необходимые действия;
  • разрушить динамическую переменную.

Операции над указателями

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

После выполнения этого оператора оба указателя P1 и P3 будут указывать на один и тот же участок памяти. Однако будьте осторожны при операциях с указателями. Только что сделанное присваивание приведет к тому, что память, выделенная ранее для указателя P3, будет потеряна. Отдавая программе участок свободной памяти, система помечает его как занятый. После работы вся память, которая была выделена динамически, должна быть возвращена системе. Поэтому изменение значения указателя P3 без предварительного освобождения связанной с ним динамической переменной является ошибкой.

Использование одинаковых значений в разных указателях открывает некоторые интересные возможности. Так после оператора P3 := P1 изменение значения переменной P3^ будет равносильно изменению значения P1^.

В этом нет ничего удивительного, так как указатели P1 и P3 указывают на одну и ту же физическую переменную. Просто для доступа к ней могут использоваться два имени: P1^ и P3^. Такая практика требует большой осмотрительности, поскольку всегда следует различать операции над адресами и операции над данными, хранящимися в памяти по этим адресам.

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

Чаще всего операции сравнения указателей используются для проверки того, связан ли указатель с динамической переменной. Если еще нет, то ему следует присвоить значение nil (зарезервированное слово):

Установка P1 в nil однозначно говорит о том, что указателю не выделена динамическая память. Если всем объявленным указателям присвоить значение nil, то внутри программы можно легко выполнить тестирование наподобие этого:

Процедуры GetMem и FreeMem

Для динамического распределения памяти служат еще две тесно взаимосвязанные процедуры: GetMem и FreeMem. Подобно New и Dispose, они во время вызова выделяют и освобождают память для одной динамической переменной:

  • GetMem(var P: Pointer; Size: Integer) — создает в динамической памяти новую динамическую переменную c заданным размером Size и присваивает ее адрес указателю P. Переменная-указатель P может указывать на данные любого типа.
  • FreeMem(var P: Pointer [; Size: Integer] ) — освобождает динамическую переменную. Если в программе используется этот способ распределения памяти, то вызовы GetMem и FreeMem должны соответствовать друг другу. Обращения к GetMem и FreeMem могут полностью соответствовать вызовам New и Dispose.

Следующий отрывок программы даст тот же самый результат:

С помощью процедуры GetMem одной переменной-указателю можно выделить разное количество памяти в зависимости от потребностей. В этом состоит ее основное отличие от процедуры New.

В данном случае для указателя P4 выделяется меньше памяти, чем может уместиться в переменной типа ShortString, и программист сам должен обеспечить невыход строки за пределы выделенного участка.

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

ReallocMem(var P: Pointer; Size: Integer) — освобождает блок памяти по значению указателя P и выделяет для указателя новый блок памяти заданного размера Size. Указатель P может иметь значение nil, а параметр Size — значение 0, что влияет на работу процедуры:

  • если P = nil и Size = 0, процедура ничего не делает;
  • если P = nil и Size <> 0, процедура выделяет новый блок памяти заданного размера, что соответствует вызову процедуры GetMem.
  • если P <>nil и Size = 0, процедура освобождает блок памяти, адресуемый указателем P и устанавливает указатель в значение nil. Это соответствует вызову процедуры FreeMem, с той лишь разницей, что FreeMem не очищает указатель;
  • если P <>nil и Size <> 0, процедура перевыделяет память для указателя P. Размер нового блока определяется значением Size. Данные из прежнего блока копируются в новый блок. Если новый блок больше прежнего, то приращенный участок остается неинициализированным и содержит случайные данные.

Представление строк в памяти

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

Физически переменная строкового типа представляет собой указатель на область динамической памяти, в которой размещаются символы. Например, переменная S на самом деле представляет собой указатель и занимает всего четыре байта памяти (SizeOf(S) = 4):

При объявлении этот указатель автоматически инициализируется значением nil. Оно показывет, что строка является пустой. Функция SetLength, устанавливающая размер строки, на самом деле резервирует необходимый по размеру блок динамической памяти и записывает его адрес в строковую переменную:

За оператором присваивания строковых переменных на самом деле кроется копирование значения указателя, а не копирование блока памяти, в котором хранятся символы.

Такой подход весьма эффективен как с точки зрения производительности, так и с точки зрения экономного использования оперативной памяти. Его главная проблема состоит в том, чтобы обеспечить удаление блока памяти, содержащего символы строки, когда все адресующие его строковые переменные прекращают свое существование. Эта проблема эффективно решается с помощью механизма подсчета количества ссылок (reference counting). Для понимания его работы рассмотрим формат хранения строк в памяти подробнее.

Пусть в программе объявлены две строковые переменные:

И пусть в программе существует оператор, присваивающий переменной S1 значение некоторой функции:

Для хранения символов строки S1 по окончании ввода будет выделен блок динамической памяти. Формат этого блока после ввода значения ‘Hello’ показан на рисунке 13:

Рисунок 13. Представление строковых переменных в памяти

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

Если в программе встречается оператор присваивания значения одной строковой переменной другой строковой переменной,

то, как мы уже сказали, копия строки в памяти не создается. Копируется только адрес, хранящийся в строковой переменной, и на единицу увеличивается количество ссылок на строку (рисунок 14).

Рисунок 14. Результат копирования строковой переменной S1 в строковую переменную S2

При присваивании переменной S1 нового значения (например, пустой строки):

количество ссылок на предыдущее значение уменьшается на единицу (рисунок 15).

Рисунок 15. Результат присваивания строковой переменной S1 нового значения (пустой строки)

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

Интересно, а что происходит при изменении символов строки, с которой связано несколько строковых переменных? Правила семантики языка требуют, чтобы две строковые переменные были логически независимы, и изменение одной из них не влияло на другую. Это достигается с помощью механизма копирования при записи (copy-on-write).

Например, в результате выполнения операторов

получим следующую картину в памяти (рисунок 16):

Рисунок 16. Результат изменения символа в строке S1

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

Все, что было сказано выше о представлении в памяти строк, относится только к строкам формата AnsiString. Строки формата WideString тоже хранятся в динамической памяти, но для них не поддерживаются механизм подсчета количества ссылок и механизм копирования по записи. Операция присваивания строковых переменных формата WideString означает выделение нового блока динамической памяти и полное копирование в него всех символов исходной строки. Что же касается коротких строк, то они целиком хранятся по месту объявления: или в области данных программы (если это глобальные переменные), или на стеке (если это локальные переменные). Динамическая память вообще не используется для хранения коротких строк.

Динамические массивы

Одним из мощнейших средств языка Delphi являются динамические массивы. Их основное отличие от обычных массивов заключается в том, что они хранятся в динамической памяти. Этим и обусловлено их название. Чтобы понять, зачем они нужны, рассмотрим пример:

Задать размер массива A в зависимости от введенного пользователем значения невозможно, поскольку в качестве границ массива необходимо указать константные значения. А введенное пользователем значение никак не может претендовать на роль константы. Иными словами, следующее объявление будет ошибочным:

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

  • На какое количество элементов объявить массив?
  • Что делать, если пользователю все-таки понадобится большее количество элементов?

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

Такое решение проблемы является неоптимальным. Если пользователю необходимо всего 10 элементов, программа работает без проблем, но всегда использует объем памяти, необходимый для хранения 100 элементов. Память, отведенная под остальные 90 элементов, не будет использоваться ни Вашей программой, ни другими программами (по принципу «сам не гам и другому не дам»). А теперь представьте, что все программы поступают таким же образом. Эффективность использования оперативной памяти резко снижается.

Динамические массивы позволяют решить рассмотренную проблему наилучшим образом. Размер динамического массива можно изменять во время работы программы.

Динамический массив объявляется без указания границ:

Переменная DynArray представляет собой ссылку на размещаемые в динамической памяти элементы массива. Изначально память под массив не резервируется, количество элементов в массиве равно нулю, а значение переменной DynArray равно nil.

Работа с динамическими массивами напоминает работу с длинными строками. В частности, создание динамического массива (выделение памяти для его элементов) осуществляется той же процедурой, которой устанавливается длина строк — SetLength.

Изменение размера динамического массива производится этой же процедурой:

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

При уменьшении размера динамического массива лишние элементы теряютяся.

При увеличении размера динамического массива добавленные элементы не инициализируются никаким значением и в общем случае их значения случайны. Однако если динамический массив состоит из элементов, тип которых предполагает автоматическую инициализацию пустым значением (string, Variant, динамический массив, др.), то добавленная память инициализируется нулями.

Определение количества элементов производится с помощью функции Length:

Элементы динамического массива всегда индексируются от нуля. Доступ к ним ничем не отличается от доступа к элементам обычных статических массивов:

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

Освобождение памяти, выделенной для элементов динамического массива, осуществляется установкой длины в значение 0 или присваиванием переменной-массиву значения nil (оба варианта эквивалентны):

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

Также, как и при работе со строками, при присваивании одного динамического массива другому, копия уже существующего массива не создается.

В приведенном примере, в переменную B заносится адрес динамической области памяти, в которой хранятся элементы массива A (другими словами, ссылочной переменной B присваивается значение ссылочной переменной A).

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


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

Не смотря на сильное сходство динамических массивов со строками, у них имеется одно существенное отличие: отсутствие механизма копирования при записи (copy-on-write).

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

Кроме стандартных строк ShortString и AnsiString, в языке Delphi поддерживаются нуль-терминированные строки языка C, используемые процедурами и функциями Windows. Нуль-терминированная строка представляет собой индексированный от нуля массив ASCII-символов, заканчивающийся нулевым символом #0. Для поддержки нуль-терминированных строк в языке Delphi введены три указательных типа данных:

Типы PAnsiChar и PWideChar являются фундаментальными и на самом деле используются редко. PChar — это обобщенный тип данных, в основном именно он используется для описания нуль-терминированных строк.

Ниже приведены примеры объявления нуль-терминированных строк в виде типизированных констант и переменных:

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

переменная S3 получит адрес уже существующей строки ‘Object Pascal’.

Для удобной работы с нуль-терминированными строками в языке Delphi предусмотрена директива $EXTENDEDSYNTAX. Если она включена (ON), то появляются следующие дополнительные возможности:

  • массив символов, в котором нижний индекс равен 0, совместим с типом PChar;
  • строковые константы совместимы с типом PChar.
  • указатели типа PChar могут участвовать в операциях сложения и вычитания с целыми числами; допустимо также вычитание (но не сложение!) указателей.

В режиме расширенного синтаксиса допустимы, например, следующие операторы:

В языке Delphi существует богатый набор процедур и функций для работы с нуль-терминированными строками (см. справочник по среде Delphi).

Переменные с непостоянным типом значений

Тип данных Variant

В среде Delphi определен стандартный тип данных Variant, с помощью которого объявляются переменные с непостоянным типом значений. Такие переменные могут принимать значения разных типов данных в зависимости от типа выражения, в котором используются. Следующий пример хорошо демонстрирует мощь переменных с непостоянным типом значений:

Значения переменных с типом Variant

Переменные с непостоянным типом содержат целые, вещественные, строковые, булевские значения, дату и время, массивы и др. Кроме того, переменные с типом Variant принимают два специальных значения: Unassigned и Null.

Значение Unassigned показывает, что переменная является нетронутой, т.е. переменной еще не присвоено значение. Оно автоматически устанавливается в качестве начального значения любой переменной с типом Variant.

Значение Null показывает, что переменная имеет неопределенное значение. Если в выражении участвует переменная со значением Null, то результат всего выражения тоже равен Null.

Переменная с типом Variant занимает в памяти 16 байт. В них хранятся текущее значение переменной (или адрес значения в динамической памяти) и тип этого значения.

Тип значения выясняется с помощью функции

Возвращаемый результат формируется из констант, перечисленных в таблице 2.10. Например, следующий условный оператор проверяет, содержит ли переменная строку (массив строк):

Код типа

Значение

Описание varEmpty $0000 Переменная содержит значение Unassigned. varNull $0001 Переменная содержит значение Null. varSmallint $0002 Переменная содержит значение типа Smallint. varInteger $0003 Переменная содержит значение типа Integer. varSingle $0004 Переменная содержит значение типа Single. varDouble $0005 Переменная содержит значение типа Double. varCurrency $0006 Переменная содержит значение типа Currency. varDate $0007 Переменная содержит значение типа TDateTime. varOleStr $0008 Переменная содержит ссылку на строку формата Unicode в динамической памяти. varDispatch $0009 Переменная содержит ссылку на интерфейс IDispatch (интерфейсы рассмотрены в главе 6). varError $000A Переменная содержит системный код ошибки. varBoolean $000B Переменная содержит значение типа WordBool. varVariant $000C Элемент варьируемого массива содержит значение типа Variant (код varVariant используется только в сочетании с флагом varArray). varUnknown $000D Переменная содержит ссылку на интерфейс IUnknown (интерфейсы рассмотрены в главе 6). varShortint $0010 Переменная содержит значение типа Shortint varByte $0011 Переменная содержит значение типа Byte. varWord $0012 Переменная содержит значение типа Word varLongword $0013 Переменная содрежит значение типа Longword varInt64 $0014 Переменная содержит значение типа Int64 varStrArg $0048 Переменная содержит строку, совместимую со стандартом COM, принятым в операционной системе Windows. varString $0100 Переменная содержит ссылку на длинную строку. varAny $0101 Переменная содержит значение любого типа данных технологии CORBA Флаги varTypeMask $0FFF Маска для выяснения типа значения. varArray $2000 Переменная содержит массив значений. varByRef $4000 Переменная содержит ссылку на значение.

Таблица 10. Коды и флаги варьируемых переменных

позволяет вам преобразовать значение варьируемой переменной к нужному типу, например:

Пока это все, что нужно знать о типе Variant, но мы к нему еще вернемся при обсуждении технологии COM Automation.

Delphi + ассемблер

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

Встроенный ассемблер

Пользователю предоставляется возможность делать вставки на встроенном ассемблере в исходный текст на языке Delphi.

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

На одной строке можно поместить несколько операторов ассемблера, разделенных двоеточием. Если каждый оператор размещен на отдельной строке, двоеточие не ставится.

В языке Delphi имеется возможность не только делать ассемблерные вставки, но писать процедуры и функции полностью на ассемблере. В этом случае тело подпрограммы ограничивается словами asm и end (а не begin и end), между которыми помещаются инструкции ассемблера. Перед словом asm могут располагаться объявления локальных констант, типов, и переменных. Например, вот как могут быть реализованы функции вычисления минимального и максимального значения из двух целых чисел:

Обращение к этим функциям имеет привычный вид:

Подключение внешних подпрограмм

Программисту предоставляется возможность подключать к программе или модулю отдельно скомпилированные процедуры и функции, написанные на языке ассемблера или C. Для этого используется директива компилятора $LINK и зарезервированное слово external. Директива указывает подключаемый объектный модуль, а external сообщает компилятору, что подпрограмма внешняя.

Предположим, что на ассемблере написаны и скомпилированы функции Min и Max, их объектный код находится в файле MINMAX.OBJ. Подключение функций Min и Max к программе на языке Delphi будет выглядеть так:

В модулях внешние подпрограммы подключаются в разделе implementation.

Итоги

Все, что вы изучили, называется языком Delphi. Мы надеемся, что вам понравились стройность и выразительная сила языка. Но это всего лишь основа. Теперь пора подняться на следующую ступень и изучить технику объектно-ориентированного программирования, без которого немыслимо стать профессиональным программистом. Именно этим вопросом в рамках применения объектов в среде Delphi мы и займемся в следующей главе.

Дополнительная информация

За дополнительной информацией обращайтесь в компанию Interface Ltd.

FreeMem — Процедура Delphi

Выбирай что больше нравится:

var P: PChar;
begin
GetMem(P, 1024);
FreeMem(P); // Вариант 1

New(P);
Dispose(P); // Вариант 2

GetMem(P, 1024);
Dispose(P); // Вариант 3

New(P);
FreeMem(P); // Вариант 4

GetMem(P, 1024);
FreeMem(P, $10000000); // Вариант 5

New(P);
FreeMem(P, $10000000) // Вариант 6
end;

Все FreeMem‘ы генерируют абсолютно один и тот же код.

New и GetMem — тоже (если в GetMem задать размер данных, на которые указывает указатель).

А вот Dispose и FreeMem отличаются тем, что первый передаёт в регистре edx размер данных, а в eax — указатель:

FreeMem — Процедура Delphi

Доброго времени суток :)
Кто нибудь может объяснить такой вот нонсенс
создаю пустое приложение, только форма и две кнопки
на первой кнопке стоит процедура
procedure TForm1.Button1Click(Sender: TObject);
begin
GetMem(p,10*1024*1024);
end;
на второй
procedure TForm1.Button2Click(Sender: TObject);
begin
freeMem(p);
end;
где p глобальная переменная и описанна как
p:^Byte;

запускаю программу, открываю TaskManager
наблюдаю следующую картину
1.запущено приложение, памяти 2616к
2.мышкой поводил над кнопочками, памяти 2636
3.еще поелозил мышкой по форме, памяти уже 2652
4.переключился на TaskManager памяти уже 2744к
6.нажимаю на первую кнопочку, памяти 2776к
(я просил 10к, выделило 32к, ну ладно может размер блока такой)
7.нажимаю на вторую кнопочку и освобождаю память, выделенную под Р, в менеджере память занятая приложением 2748к. т.е. освободилось 28672б.
7.переключился на таск менеджера, память программы опять увеличлась до 2752к

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

забыл добавить, если в следующий раз опять нажать на кнопку №1
то память увеличится на 28672б
и при нажатии на кнопку №2
уменьшется на 28672
:)
и так до бесконечности

пощел дальше проверять
сделал массив глобальный, указателей, на 10 эл-тов
и на первой кнопочке сделал

procedure TForm1.Button1Click(Sender: TObject);
Var i:Integer;
begin
for i:=1 to 10 do
GetMem(p[i],10*1024*1024);
end;

procedure TForm1.Button2Click(Sender: TObject);
Var i:Integer;
begin
For i:=1 to 10 do
freeMem(p[i]);
end;

:))
при нажатии на первую кнопку
по менеджеру задач, памяти выделилось 300Кб а освободилось, после нажатия на вторую 296 4 кило кудато делись :(((
но черт побери я просил 100к выделить
а выделили 300.
жуть

Не обращайте внимания, это не утечка. Менеджер памяти в Delphi выделяет не столько, сколько запрошено, а с запасом; и отдавать всю освободившуюся память тоже не спешит. Сделано это специально, чтобы реже выполнять дорогостоящую операцию запроса памяти.

не надо смотреть на task manager


> Юрий Зотов © (11.05.09 22:41) [3]

верю что с запасом
но както в три раза больше выделать
:((
я в цикле попросил выделить 100 блоков по 10к
1мб
выделилось 3мб!!
а ежели я попрошу выделить 300к.
выделиться 1Гиг.
так чтоли??

или при запросе 100кб
он выделяет 300кб
но если я прпошу при выделенных 100кб
еще 100кб, то он разместит их в этих 300кб или запросит еще 300 у ОС ??


> Юрий Зотов © (11.05.09 22:41) [3]


> Игорь Шевченко © (11.05.09 22:42) [4]

ребята, объясните тогда ошибку OUT of MEMORY
ежели я вот в этом коде нажму на кнопку 1 а потом на кнопку 3

Type TArray=Array[1..10*1024*1024] of Byte;
pArray=^TArray;
Var p:Array[1..100] of ^Byte;
p2:Array[1..100] of PArray;

procedure TForm1.Button1Click(Sender: TObject);
Var i:Integer;
begin
for i:=1 to 100 do
GetMem(p[i],10*1024*1024);
end;

procedure TForm1.Button2Click(Sender: TObject);
Var i:Integer;
begin
For i:=1 to 100 do
freeMem(p[i]);
end;

procedure TForm1.Button3Click(Sender: TObject);
Var i:Integer;
begin
For i:=1 to 100 do
New(p2[i]);
end;

procedure TForm1.Button4Click(Sender: TObject);
Var i:Integer;
begin
for i:=1 to 100 do
Dispose(p2[i]);
end;

решил проверить отличаются выделения памяти для проуедур New и GetMem
нажал на первую, надал на третью и ошибка о нехватке памяти :(((
я в ступоре
ну первый раз метр выделить
и второй раз метр выделить
памяти на компе , честно, хватит 10 раз так выделить :)


> procedure TForm1.Button3Click(Sender: TObject);
> Var i:Integer;
> begin
> For i:=1 to 100 do
> New(p2[i]);
> end;

Это не метр, это 100*10 метров.


> Это не метр, это 100*10 метров.

я тоже думал что это метры
но вот эта штука
GetMem(p[i],10*1024*1024);

выделяет 10к!! (т.е. 32к! )
но не 10 метров!!
во всяком случае так в менеджере задач
и если я нажму на кнопку №3
то в менеджере задач мне отображается что выделено 3Мб.
чесслово!! могу скриншот сделать


> ford ©

Используй FastMM4, MemProof и т.д. но не смотри ты в диспетчер задач. На основании его показаний что-либо понять довольно сложно.

хорошо, убедили, не буду смотреть в таск менеджер Ж)
но тогда всервано загадка
я прошу выделить вмне два раза по 1 гигу
файл подкачки, аж 2.5 гига
+ оперативка 825м
ну, т.е. должно хватитьто
а оно мне Out of memory


> ford © (11.05.09 23:20) [11]


> хорошо, убедили, не буду смотреть в таск менеджер Ж)

Можешь смотреть, только смотри хотя бы столбец Виртуальная память.


> я прошу выделить вмне два раза по 1 гигу


> а оно мне Out of memory

Так и должно быть

> ford © (11.05.09 23:20) [11]

> я прошу выделить вмне два раза по 1 гигу

Вся память приложения — 4 гига (предел адресации в 32-битной системе).
Из них 2 верхних гига и нижние 64 Кб — зарезервировано.
Часть оставшейся памяти занимает сама программа и стек.


Откуда же взять еще 2 раза по гигу?


> Юрий Зотов © (11.05.09 23:30) [13]

убедил
интересно тогда ну 64 нижней это я в курсе кому
а кому 2 гига верхней то достались??


> а кому 2 гига верхней то достались??

ОС


> DVM © (11.05.09 23:45) [15]
>
> > а кому 2 гига верхней то достались??
>
> ОС
>

ууу жадина
:)

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


> ну сделаи бы безлимитную т.е. ограниченную размером свопа

Безлимитную нельзя, у тебя указатели 32-битные. Потому максимум 4Гб.
Если необходимы большие объемы памяти надо переходить в 64 разрядную версию Windows


> то в менеджере задач мне отображается что выделено 3Мб!!
> !

не надо смотреть на task manager

Сколько раз надо повторить, чтобы дошло ?

флудеры маст дай

> ford © (11.05.09 23:54) [16]

Она не жадина. Ей DLL куда-то грузить надо.


> DVM © (11.05.09 23:57) [17]

ну погодите, ежели так посмотреть то в ДОСе тоже был лимит на 64к
но сделали . блин уже забыл как назывались, вощем адресовали как номер блока:смещение в блоке
конечно это возврат к старому, но всетаки зато практически снимается ограничение на память :)

хотя может я и не прав

> ford © (12.05.09 02:00) [21]

Сегмент (word) : Смещение (word)

Word — потому, что регистры 16-битные. То есть, размер сегмента не может превышать 64К. Это и есть ограничение, о котором Вы говорите.

И ограничено это было 1 мегабайтом.


> ford © (12.05.09 02:00) [21]


> конечно это возврат к старому, но всетаки зато практически
> снимается ограничение на память :)

Зачем городить такой огород, когда есть 64 разрядные ОС.

> [21] ford © (12.05.09 02:00)
> хотя может я и не прав

Наверно архитектуру процессоров 80х86, не знаешь? А уже несколько лет х64 как.:)


> Игорь Шевченко © (11.05.09 22:42) [4]
> не надо смотреть на task manager
> Сколько раз надо повторить, чтобы дошло ?

Повторять, повторять, повторять.

Аффтар, читай книжки (Рихтера, например) до полного просветления.


> Игорь Шевченко © (11.05.09 22:42) [4]
>
> не надо смотреть на task manager

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

Pchar и освобождение памяти

Здравствуйте.
Написал парсинг страницы сайта. Запрос страницы идёт из основной программы, после чего полученная строка передаётся в dll где происходит изъятие необходимой мне информации и передача обратно в программу.

Так как в dll не рекомендуется передавать string преобразую строку в pchar, и в dll обратно преобразую в string для работы, после получения результата процедуру преобразования проделываю снова.
Я бы остановился на этом, но при закрытии программы вылетает ошибка

. При запуске без Delph приложение так и остаётся висеть в памяти.
Подскажите, требуется очистка памяти?

При выполнении этой процедуры ругается на :
FreeMem(site);
FreeMem(category);

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

У меня есть много распределения памяти и одинаковое количество FreeMem вызовов. То, что у меня не было, хотя это проверка перед вызовом FreeMem, чтобы увидеть, если указатель был равен нулю, и линия после освобождения, чтобы установить указатель на ноль.

Я пытался создать функцию, чтобы сделать это

Но есть проблема. Он не может установить оригинальный указатель на ноль, поскольку параметр не изменяется (вар р: Pointer). Я не могу использовать вар хотя, потому что если я компилятор жалуется тип должен быть точно такой же тип (Pointer). Указатели я передаю могут быть указатели любого типа (PChar, регулярный указатель и т.д.).

Что я могу сделать, чтобы исправить это? Есть ли лучшее решение?

Для того, чтобы иметь возможность передавать произвольные значения указателей на эту функцию, вы должны следовать той же модели, FreeAndNil и передать в нетипизированном параметре . В противном случае компилятор правильно жалуется на фактические и формальные типы параметров не являются идентичными. Введите отливку нетипизированного параметра для указателя при вызове FreeMem на нем.

Вы делаете пару бессмысленных вещей в этой функции.

Прежде всего в том, что освобождая всухую указатель всегда безопасно, так что нет никаких причин, чтобы проверить, что перед вызовом FreeMem. Это освобождает указатель не-ноль, что вам нужно беспокоиться о том, но ни одна функция не может защитить вас от этого.

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

Со всеми выше в виду, ваша функция сводится к следующему:

Будьте осторожны , чтобы случайно не назвать эту функцию на что — либо , что не является указателем выделяется GetMem. Компилятор не будет поймать его для вас , как она могла бы , если бы вы использовали типизированные параметры. Если вы пытаетесь освободить то , что не было выделено с GetMem, вы , вероятно , получите исключение EInvalidPointer, но переменная вы прошли в по- прежнему будет ноль позже. Это так же , как FreeAndNil работает.

Delphi. New и dispose вместо GetMem и FreeMem

Получаю пакеты по сети. Я знаю, что размер пакета не будет больше 2000 байт. Так как получение пакета в одном потоке, а обработка в другом, то мне нужно передать копию buf через TList. Соответственно, создаю буфер в одном потоке, а уничтожаю в другом.

Раньше использовал следующую связку

Как машина поймет, сколько нужно освободить, ведь размер неизвестен заранее? может сначала нужно сделать

Во-первых, писать второй аргумент у FreeMem не нужно. Т.е. должно быть так:

Менеджер памяти Delphi (код, который реализует GetMem / FreeMem ) просто запоминает размер выделенного блока в своих служебных структурах. Где и как он это делает — вам знать не требуется, это зависит от конкретной реализации менеджера памяти, которая может меняться. Вам достаточно знать, что FreeMem всегда корректно освободит память, выделенную ранее вызовом GetMem , AllocMem (а также — New , при условии, что финализацию вы сделаете сами) — разумеется, в предположении, что в FreeMem вы передаёте ровно тот же указатель, что вам вернула GetMem , AllocMem или New . Вам не нужно специально хранить размер выделенной области (если только этот размер не нужен вам для ваших личных целей).

Во-вторых, TIdBytes является динамическим массивом, т.е. это уже динамически выделяемая память в куче, аналог «старого» Buf . Не нужно делать на него указатель: это получается «указатель на указатель», лишний уровень косвенности. Передавайте просто переменную TIdBytes . Т.е. было:

Этот код работает в точности так же, как и предыдущий: данные Buf передаются по ссылке. Если вам нужна именно копия данных (не очень понятно, как вы хотели решить этот вопрос переходом к TIdBytes и/или использованием «указателя на указатель») — просто скопируйте массив:

Как машина поймет, сколько нужно освободить, ведь размер неизвестен заранее?

Dispose — это «волшебная» функция компилятора. У неё, на самом деле, два параметра. Первый передаёте вы: это указатель на блок памяти. Второй скрытно передаёт компилятор: это указатель на информацию о типе переменной. Соответственно, в информации типа указано, что лежит в переменной, так что функция Dispose будет знать как корректно освободить память. В данном случае там будет сказано: «в переменной лежит динамический массив», так что Dispose сделает ему SetLength в 0. Ну и SetLength , соответственно, знает размер массива, чтобы корректно его освободить.

Использование кучи в Delphi.

Программируя в Delphi мы постоянно явно или неявно взаимодействуем с менеджером кучи. Неявно его используют все функции или конструкции языка, требующие выделения памяти: создания объекта класса, создание динамического массива или длинной строки. Явное взаимодействие с этим механизмом происходит при использовании следующих функций Delphi: New, Dispose, GetMem, AllocMem, ReallocMem, FreeMem.

procedure New(var P: Pointer);

Функция выделяет динамическую память в куче. Параметром принимает типизированный указатель, затем инициирует его адресом выделенного участка. Не имеет смысла использовать функцию с нетипизированным указателем, он просто останется со значением NIL.

procedure Dispose(var P: Pointer);

Функция освобождает память в куче. Параметром принимает типизированный указатель возвращенный функцией New(). Используется только в паре с функцией выделения памяти New().

function summ( var1, var2: integer ): integer;var f_int : ^integer; s_int : ^integer; begin // выделим память new(f_int); new(s_int); f_int^ := var1; s_int^ := var2; Result := f_int^ + s_int^; // освободим память dispose(f_int); dispose(s_int); end;

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

procedure GetMem(var P: Pointer; Size: Integer);

Выделяет блок памяти указанного размера. Можно использовать нетипизированный указатель. Если свободной памяти не окажется, то будет сгенирированно исключение EOutOfMemory. После выделения в памяти содержится всякий мусор.

function AllocMem(Size: Cardinal): Pointer;

Соответствует функции GetMem(), но после выделения память будет инициирована нулями. Возвращает указатель, как результат, а не как параметр, в отличие от той же GetMem().

procedure ReallocMem(var P: Pointer; Size: Integer);

Деиствие зависит от значений P и Size. P может быть пустым указателем или содержать адрес участка памяти, возвращенный функциями GetMem, AllocMem и ReallocMem.
Варианты:
( ( P = NIL) and ( Size = 0 ) ): ReallocMem ничего ни делает;

( ( P = NIL ) and ( Size <> 0 ) ): выделяет новый блок памяти и устанавливает P на его адрес. Можно использовать вместо GetMem();

( ( P <> NIL ) and ( Size = 0 ) ): освобождает память, адресуемую P. P будет установлен в NIL. Похоже на FreeMem(), но в отличае от него чистит указатель.

( ( P <> 0 ) and ( Size <> 0 ) ): перевыделяет указанный блок памяти (изменяет его размер). Существующие данные затронуты не будут, но если память увеличиться, то новое пространство будет содержать всякий мусор. Если для изменения размера не будет хватать памяти, то блок может быть перенесен на другое место в пределах кучи, P будет указывать на новое место.

procedure FreeMem(var P: Pointer[; Size: Integer]);

Функция освобождает память, выделенную в GetMem и AllocMem. Может принимать размер памяти, которую нужно освободить. Надо быть крайне осторожным с этим параметром, так как тут может появиться утечка. После освобождения памяти указатель P будет содержать мусор. Если в качестве параметра передана структура, содержащая длинные строки, варианты, динамические массивы или интерфейсы, тогда перед выполнением FreeMem будет вызвана Finalize.

procedure useMemoryManager;type // шаблончик для доступа к памяти побайтово memcells = array[0..$7FFFFFFE] of byte; var p: pointer; i: integer; begin // выделим память GetMem(p, 100); // и инициируем ее какими-нибудь числами for i:=0 to 99 do memcells(p^)[i] := byte(i); // добавим памяти ReallocMem(p, 200); // и допишим числа for i:=0 to 99 do memcells(p^)[i+100] := byte(i+100); // … посмотрим, что получилось for i:=0 to 199 do writeln(inttostr(memcells(p^)[i])); // уберем после себя FreeMem(p);end;

Работа с менеджером памяти

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

Во-первых есть несколько глобальных переменных, управляемых менеджером.

var AllocMemCount: integer;

Эта переменная содержит количество выделенных блоков памяти. Используется для определения оставшихся блоков.
Использовать эту переменную стоит с осторожностью. Например, в статически слинкованных программах, модули будут иметь свои собственные экземпляры AllocMemCount.

var AllocMemSize: integer;

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

var HeapAllocFlags: word;

Набор флагов, устанавливающих опции для менеджера памяти. (по умолчанию – GMEM_MOVEABLE)

GMEM_FIXED Выделяет фиксированную память. Т.к. ОС не может перемещать блоки памяти в юзермод, то и нет нужды блокировать память (не может комбинироваться с GMEM_MOVEABLE)
GMEM_MOVEABLE Выделяет перемещаемую память. В юзермод блоки не могут быть перемещены, если они расположены в физической памяти, но могут перемещаться в пределах кучи.
GMEM_ZEROINIT При выделении памяти (например, функцией GetMem) все байты этой памяти будут выставлены в 0
GMEM_MODIFY Используется для изменения атрибутов выделенного блока памяти
GMEM_DDESHARE Введёны для совместимости с 16-разрядными версиями, но может использоваться для оптимизации DDE операций.
GMEM_SHARE — // —
GPTR GMEM_FIXED + GMEM_ZEROINIT
GHND GMEM_MOVEABLE + GMEM_ZEROINIT

Во-вторых, существует ряд полезных функций для управления менеджером.

function GetHeapStatus(): TheapStatus;

Узнать текущее состояние диспетчера памяти.
Структура TheapStatus:

THeapStatus = record // Размер памяти в байтах которая доступна программе TotalAddrSpace: Cardinal; // Сколько памяти из TotalAddrSpace не находятся в SWAPе TotalUncommitted: Cardinal; // Сколько памяти из TotalAddrSpace находятся в SWAPе TotalCommitted: Cardinal; // Сколько всего динамической памяти выделено программе TotalAllocated: Cardinal; // Сколько памяти еще доступно для выделения (увеличивается) TotalFree: Cardinal; // Сколько памяти доступно в маленьких блоках FreeSmall: Cardinal; // Сколько памяти доступно в больших блоках // непрерывно идущие маленькие блоки могут складываться FreeBig: Cardinal; // Доступная, но еще не выделявшаяся память Unused: Cardinal; // Размер памяти используемой для нужд менеджера Overhead: Cardinal; // Внутренний статус кучи HeapErrorCode: Cardinal; end;

Если используется SharedMem, то статус относится к куче разделяемой несколькими процессами.

Последнее изменение этой страницы: 2020-02-21; Нарушение авторского права страницы

Илон Маск рекомендует:  Что такое код dcngettext
Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL