CFA LogoCFA Logo Computer
Новости Статьи Магазин Драйвера Контакты
Новости
RSS канал новостей
В конце марта компания ASRock анонсировала фирменную линейку графических ускорителей Phantom Gaming. ...
Компания Huawei продолжает заниматься расширением фирменной линейки смартфонов Y Series. Очередное ...
Компания Antec в своем очередном пресс-релизе анонсировала поставки фирменной серии блоков питания ...
Компания Thermalright отчиталась о готовности нового высокопроизводительного процессорного кулера ...
Компания Biostar сообщает в официальном пресс-релизе о готовности флагманской материнской платы ...
Самое интересное
Программаторы 25 SPI FLASH Адаптеры Optibay HDD Caddy Драйвера nVidia GeForce Драйвера AMD Radeon HD Игры на DVD Сравнение видеокарт Сравнение процессоров

АРХИВ СТАТЕЙ ЖУРНАЛА «МОЙ КОМПЬЮТЕР» ЗА 2003 ГОД

Мысли о Паскале

Владислав ДЕМЬЯНИШИН nitromanit@mail.ru

Продолжение, начало см. в МК №46, 51-52, 4, 6-7, 10, 12-13, 16-18, 22, 24, 29, 34, 41, 46, 4, 6, 17, 21, 23, 28, 30, 32 (165, 170-171, 175, 177-178, 181, 183-184, 187-189, 193, 195, 200, 205, 212, 217, 227, 229, 240, 244, 246, 251, 253, 255).

В предыдущей статье я рассказывал о стандартных процедурах New и Dispose, позволяющих выделять и освобождать память под динамические переменные. Однако они применимы только к типизированным указателям, то есть к указателям, имеющим базовый тип. Если говорить начистоту, то эти процедуры универсальны и, ко всему прочему, они предназначаются для создания и уничтожения экземпляров объектов посредством явного вызова конструктора и деструктора соответственно. Поэтому лично я большее предпочтение отдаю двум другим стандартным процедурам —GetMem и FreeMem, о которых сегодня и пойдет речь.

Бестиповые указатели

Помимо типизированных указателей, в Turbo Pascal мы можем при помощи идентификатора pointer объявлять нетипизированные, совместимые со всеми ссылочными типами:

Легко убедиться, что нетипизированному указателю P легко может быть присвоено значение любого типизированного указателя. А вот последнее присваивание вызовет ошибку Error 26: Type mismatch (базовые типы не соответствуют).

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

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

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

Итак, для выделения памяти под динамические переменные без учета типа значений, которые будут храниться в данной области, в Turbo Pascal служат две процедуры-сестрички, и зовут их GetMem(var p : pointer; size : word) и FreeMem(var p : pointer; size : word). Первая выделяет область памяти размером Size (не должен превышать 65520 байт) и помещает ее адрес в указатель любого ссылочного типа (типизированного и нетипизированного). При этом, если запрашиваемый размер будет превышать свободную память в куче, то произойдет все та же ошибка Error 203: Heap overflow error. (куча переполнена, исчерпана), а потому актуальность использования функции MaxAvail сохраняется. Процедура FreeMem предназначена для освобождения памяти, выделенной процедурой GetMem, при этом следует освобождать столько же байт, сколько было выделено, и по той же ссылке, иначе могут быть «бедствия с последствиями» в масштабах программы. Пример:

Управление кучей

При запуске Pascal-программы под кучу по умолчанию отводится вся свободная оперативная память, доступная операционной системе. При этом не следует забывать о всевозможных загруженных драйверах, резидентах, а также сегменте данных и стеке самой программы, на суммарный объем которых свободная область оперативной памяти станет меньше. В итоге размер кучи при благоприятных условиях может достигать 450 Кб — если, конечно, краткость является родной сестрой вашего таланта и способна обеспечить минимальный размер exe-файла исполняемой программы. При этом столь большой объем кучи не всегда уместен — бывает необходимо предусмотреть возможность запускать внешние программы из вашего Pascal-приложения. Поэтому в Turbo Pascal имеется директива {$M StackSize, HeapMin, HeapMax}, где параметр StackSize задает размер сегмента стека, который может варьироваться в пределах 1024..65520 байт; HeapMin — минимальный размер кучи, который необходим для запуска программы, диапазон может быть 0..655360. Если свободной памяти окажется меньше значения данного параметра, то программа вообще не запустится; при параметре 0 программа будет запускаться всегда. Наконец, HeapMax задает максимальный (скорее, рекомендованный) объем кучи (HeapMin..655360), который будет учитываться в ситуации, когда свободной памяти оказалось больше, чем HeapMin, и тогда размер кучи не будет превышать HeapMax.

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

Стандартная процедура Mark(var p : pointer) позволяет сохранить в указатель вершину кучи, на которую указывает системная переменная HeapPtr. Данная процедура может пригодиться, например, в следующей ситуации. Допустим, в программе, начиная с некоторого момента, происходит выделение памяти под несколько буферов (динамических переменных) при помощи вызовов New или GetMem, адрес каждого заносится в соответствующий указатель. Когда же приходит время освободить память, отведенную под эти буферы, придется составить немало строк с вызовом процедуры Dispose или FreeMem для каждого указателя этих буферов. Если таких указателей пара-тройка, это еще ладно, но когда их десятки, то проще освободить их, разом вызвав стандартную процедуру Release(var p : pointer). Таким образом вершина кучи будет восстановлена. Пример:

содержит строку Release(LastHeapPtr), которая эквивалентна строкам:

Как я уже сказал, указатель по своей структуре напоминает запись с двумя полями — смещение и сегмент, — но обращаться к этим частям как к полям нельзя. Зато в системе Turbo Pascal предусмотрены стандартные операции извлечения (чтения) значения номера сегмента Seg( X ) : word и величины смещения Ofs( X ) : word из указателя.

Если к предыдущим примерам добавить следующее описание:

то выполнение конструкции

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

Исходя из того, что данные функции возвращают сегмент и смещение любой переменной, указанной в качестве параметра, следует заметить, что в конструкциях Seg(PL^) и Ofs(PL^) фигурирует разыменованный указатель PL — это чтобы можно было получить сегмент и смещение адреса области памяти, к которой отсылает этот указатель. Если же применить соответственно конструкции Seg(PL) и Ofs(PL), то будут получены сегмент и смещение адреса самой переменной PL, а не области памяти, на которую она указывает.

Извлекать сегмент и смещение из указателя мы научились. А что если необходимо осуществить обратное действие, то есть сформировать указатель на некоторую область в памяти? Для этого существует стандартная функция Ptr( Seg, Ofs : word ) : pointer, которая объединяет оба параметра — сегмент и смещение — в единое значение нетипизированного указателя. Если к предыдущим примерам добавить

то мы тем самым сформируем адрес поля хранения счетчика системных часов, находящийся в области данных BIOS, и поместим указатель в ссылочную переменную PL. Затем, до тех пор, пока не будет нажата клавиша «Any key» :-) , в теле цикла будет происходить считывание системных часов и преобразование их в формат HH:MM:SS.MS, то бишь ЧАСЫ:МИНУТЫ:СЕКУНДЫ.МИЛЛИСЕКУНДЫ:

Ну а тем, у кого клавиши «Any key» на клавиатуре не нашлось, нам остается лишь посочувствовать :-).

Ранее в моих статьях уже упоминалась операция взятия адреса, где символ @ ставился перед идентификатором переменной или подпрограммы, и такая конструкция возвращала указатель (адрес) на соответствующую переменную или подпрограмму. В Turbo Pascal имеется эквивалентная функция Addr( X ) : pointer:

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

Ошибки в работе с указателями

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

в процедуре CalcArr выделяется память под массив, после чего адрес заносится в локальную переменную-указатель Arr, затем с массивом выполняются некоторые действия, и процедура завершается. Но перед завершением процедуры не выполнена команда FreeMem(Arr,SizeOf(TArr)), и в итоге на экране будет выведена информация, красноречиво подтверждающая, что объем свободной памяти уменьшился ровно на размер массива, то есть память, отведенная под массив, осталась не освобожденной, да и к тому же ссылка на неосвобожденную область безвозвратно потеряна вместе с содержимым локальной переменной Arr.

Хотя, если в блок описания глобальных переменных добавить

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

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

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

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

В процедуре Proc1 в типизированный указатель PL заносится адрес локальной переменной типа longint, то есть адрес фрагмента памяти стека. Затем вызывается процедура Proc2, в которой по роковой случайности совершенно одинаково описана локальная переменная типа longint. В итоге, разыменованный указатель PL^ уже возвращает другой результат. И напоследок выполняется процедура Proc3, в которой описана локальная переменная типа single, размещенная на том самом месте в стеке, куда указывает ссылка PL. Но значение этого фрагмента памяти интерпретируется как значение типа longint, поэтому будет получен третий, еще более умопомрачительный результат.

И на последок хочу предостеречь от очень распространенной ошибки среди новичков. При вызове процедур, которые принимают фактический параметр как бестиповый, последние часто указывают ссылку на динамическую переменную, забывая разыменовать ее. Поэтому при использовании таких процедур как Move, BlockRead, BlockWrite следует четко осознавать свои действия.

(Продолжение следует)

Рекомендуем ещё прочитать:






Данную страницу никто не комментировал. Вы можете стать первым.

Ваше имя:
Ваша почта:

RSS
Комментарий:
Введите символы или вычислите пример: *
captcha
Обновить





Хостинг на серверах в Украине, США и Германии. © sector.biz.ua 2006-2015 design by Vadim Popov