Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Иванова Г.С. - Основы программирования

.pdf
Скачиваний:
2769
Добавлен:
02.04.2015
Размер:
13.53 Mб
Скачать

 

6. Файловая система. Файлы

 

 

Begin

 

 

 

 

 

 

 

ClrScr;

 

 

 

 

 

 

 

WriteLnf'Введите имя

файла:');

 

 

 

ReadLn(name);

 

 

 

 

 

 

 

Assign(fhname);

 

 

 

 

 

 

{$!-} ReSet(ft);{$!+}

{проверяем наличие файла с именем name}

iflOResulto

Othen

 

 

 

 

 

 

begin

 

 

 

 

 

 

 

WriteLn(#7, * нет файла с именем \namej;

 

Halt;

 

 

 

 

 

 

 

end;

 

 

 

 

 

 

 

GeiFTime(fiyiime);

{определяем дату создания файла}

 

UnPackTifne(time,date);

{распаковываем дату}

 

 

WriteLn(^ama

создания файла= \date.year:5,date.month:3,

date,day:3);

WriteLnCВремя создания файла = \dateMour:3,date,min:3ydate.sec:3);

with date do

 

 

 

 

 

 

 

begin year:=200J;

month: =3; day: =8

end;

 

PackTime(date,tinte);

{упаковываем дату}

 

 

SetFTime(fi,time);

{меняем дату}

 

 

WriteLn(*Пocлe изменения даты: ');

 

 

WriteLn('dama

создания файла =

\date,year:5,date,month:3,date.day:3);

WriteLn(*вpeмя создания файла

=\date.hour:3,date,min:3,date,sec:3);

FSplit(nameypathf,namef,extJ);

{расщепляем имя файла}

WriteLn(*Пoлнoe имя

файла

=\pathf:25,namef:J2,extf:8);

 

GetFAttr(ft,k);

{определяем атрибуты файла}

 

atr:=lo(k);

 

 

 

 

 

 

 

WriteLnC байт атрибутов \atr);

 

 

size: =DiskFree(l);

 

 

 

 

 

 

WriteLn(*Ceo6odHoe место на диске А \size:10, * байт

');

Close(fi);

 

 

 

 

 

 

 

End

 

 

 

 

 

 

 

Результат работы программы:

 

 

 

 

Введите имя файла:

 

 

 

 

 

c:\iva\primer.pas\file\primer.txt

 

 

 

Дата создания файла = 2001 3 7

 

 

Время создания файла = 10 47 22

 

 

После изменения даты

 

 

 

 

дата создания файла = 2001

3 8

 

 

время создания файла = 10 47 22

 

 

Полное имя

файла =

c:\iva\primer.pas\file\

primer .txt

байт атрибутов 32

 

 

 

 

 

 

Свободное место на диске А

 

9104 байт

 

 

211

?• ПРОГРАММИРОВАНИЕ С ИСПОЛЬЗОВАНИЕМ ДИНАМИЧЕСКОЙ ПАМЯТИ

До настоящего момента мы имели дело с переменными, которые размещаются в па­ мяти согласно вполне определенным правилам. Так, память под глобальные переменные программы выделяется в процессе компиляции, и эти переменные существуют в течение всего времени работы программы. Для локальных переменных, описанных в подпро­ грамме, память отводится при вызове подпрограммы, при выходе из нее эта память ос­ вобождается, а сами переменные прекращают свое существование. Иными словами, рас­ пределение памяти во всех случаях осуществляется полностью автоматически. Перемен­ ные, память под которые выделяется описанным образом, называют статическими. Под эту категорию подпадают все переменные, объявленные в области описаний программ­ ных блоков. Однако Borland Pascal предоставляет возможность создавать новые перемен­ ные во время работы программы, сообразуясь с потребностями решаемой задачи, и унич­ тожать их, когда надобность в них отпадает.

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

7.1. Указатели и операции над ними

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

Память принято делить на слова, двойные слова и параграфы. Слово имеет длину 2 байта, двойное слово ~ 4 байта, а параграф - 16 байт.

При работе с памятью используется адресация по схеме «база + смеще­ ние» (рис. 7.1). При этом адрес конкретного байта М определяется как адрес некоторого заданного байта А5 (адрес базы) + расстояние до требуемого бай­ та AQ^ (смещение),

212

7. Программирование

с использованием динамической памяти

В микропроцессорах

фирмы Intel

 

(начиная с i8086) в качестве адреса базы

0 1 2 3 4 5

используют адреса, кратные 16. Четыре

последних бита такого адреса равны О, и

М

их не хранят, а аппаратно добавляют при

 

вычислении физического адреса.

Рис. 7.1. Адресация по схеме

Непрерывный участок памяти, име­

«База + смещение»

ющий длину не более 64 КБ и начинаю­

 

щийся с адреса, кратного 16 (0,16,32, ), называют сегментом. Адрес нача­ ла сегмента принимают за базу для всего сегмента. Адрес базы сегмента без последних четырех бит называют сегментным.

Сегментный адрес и смещение имеют размер по 16 бит (слово). Физи­ ческий адрес, получаемый при их сложении с учетом отброшенных четырех бит (рис. 7.2), имеет размер 20 бит и может адресовать память объемом 2^^ байт или 1 МБ.

Максимальное смещение равно 2^^-1, что соответствует 64 КБ памяти. Таким образом, относительно одной базы можно адресовать не более 64 КБ памяти, что ограничивает размер сегмента.

Примечание. Современные модели микропроцессоров используют адреса большей дли­ ны с отличающейся схемой получения физического адреса, что учитывается версиями Pascal, предназначенным для работы «под Windows», но принцип адресации по схеме «база+смещение» используется и там.

Программа и данные хранятся в памяти фрагментами, каждый из кото­ рых расположен в своем сегменте. Различают три вида сегментов: кодов, данных и стека. В сегментах кодов хранится собственно программа. В сег­ ментах данных размещаются глобальные переменные и константы. Сегмент стека интенсивно используется в процессе выполнения программы: при вы­ зове подпрограмм в стек записывается адрес возврата, в нем размещаются локальные переменные, копии параметров-значений, адреса параметров-пе­ ременных и параметров-констант и т.п. (см. фрейм активации в парагра­ фе 5.6).

В процессе работы сегментные адреса хранятся в специальных сегмент­

ных регистрах:

16 бит

CS - адрес базы сегмента кодов;

Сегментный адрес

0000

DS - адрес базы сегмента данных; +

Смещение

 

SS - адрес базы сегмента стека.

Доступ к конкретным участ­ кам сегмента осуществляется через

Физический адрес

20 бит

соответствующие смещения.

Рис. 7.2. Получение физического адреса

213

Часть 1. Основы алгоритмизации и процедурное программирование

Сегментный

Смещение

адрес

 

2 байта

2 байта

Рис. 7.3. Структура записи адреса в память

При записи адреса в память отдельно со­ храняются сегментный адрес и смещение (рис. 7.3).

В Borland Pascal для работы с адресами ис­ пользуется специальный тип данных - указа­ тель. Данные этого типа включают два поля ти­ па word и хранят соответственно сегментный

адрес и смещение.

Различают указатели двух типов: типизированные и нетипизированные. Типизированные указатели содержат адреса, по которым в памяти раз­ мещаются данные определенных типов. Используя эти указатели с данными указанных типов, можно выполнять операции, предусмотренные базовым типом. Синтаксическая диаграмма объявления типизированного указателя

приведена на рис. 7.4. Например:

Туре tpi=4nteget;

{объявляем тип «указатель на целое»}

Varpi.tpi;

{объявляем переменную этого типа}

или без предварительного объявления типа:

Varpi: ^integer; {объявляем переменную типа «указатель на целое»}

Нетипизированные указатели хранят просто адреса, которые не связа­ ны с данными конкретных типов. Для их объявления используют зарезерви­ рованное слово pointer. Например:

Varр:pointer;...

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

Турерр = ^регсоп; {тип person еще не определен!}

регсоп = record {определение типа person}

пате: string: next: рр;

end;...

-<А Идентификатор базового типа

Рис. 7.4. Синтаксическая диаграмма <Объявление типизированного указателя>

Для указателей, которые не хранят ни­ каких адресов, введена константа «нулевой адрес» с именем nil. Константу nil можно присваивать указателю любого типа.

Инициализация указателей. Для объ­ явления инициализированных указателей используют типизированные константы, но

214

7. Программирование с использованием динамической памяти

единственное значение, которое может быть присвоено указателю при ини­ циализации - это значение nil. Например:

Constp:^real=nil;...

Операции над указателями. Над значениями указателей возможны следующие операции.

Присваивание, При выполнении этой операции указателю присваивает­ ся значение другого указателя или nil. Допускается присваивать указателю только значение того же или неопределенного типа.

Например:

Var р1, р2: ^integer; рЗ: ^real;

р: pointer;

{допустимые операции}

pJ:=p2; р:=рЗ; р1:=р; pl:=nil; р:=пП;

(недопустимые операции}

рЗ:=р2; pJ:=p3;,..

Получение адреса. Это унарная операция, которая строится из знака операции - символа @ (коммерческое а) и одного операнда - переменной любого типа. Результат операции - указатель типа pointer, который можно присвоить любому указателю.

Например:

Var i.integer;

pi: ^integer;...

pi:=@i; {указатель pi будет содержать адрес переменной i}

Доступ к данным по указателю (операция разыменования). Чтобы по­ лучить доступ к переменной по указателю, необходимо после переменной - типизированного указателя поставить знак «'^». Полученное значение имеет тип, совпадающий с базовым типом указателя. Нетипизированные указатели разыменовывать нельзя.

Например:

j:=pi^; {переменной] присваивается значение целого, расположенно­ го по адресу pi}

pi^:=pi^-^2; {целое значение, расположенное по адресу pi, увеличива­ ется на 2}

В табл. 7.1 показано, как выполняются операции с указателями.

215

Часть 1. Основы алгоритмизации и процедурное программирование

 

 

 

 

Т а б л и ц а

Фрагмент

Результат

 

Описание операции

программы

операции

 

 

 

Const i:integer=];

pi

1

 

Создается инициализированная

Т

 

Var pi: ^integer;

1

^

 

переменная i и указатель на це­

 

?

1

лое pi

 

 

1 ^

 

pi

pi:=@i;

\ 1

N'1 1 1

Указателю pi присваивается ад­ рес переменной i

pi^:^pi^j^2;

pi

 

 

 

\ 1

 

 

 

 

 

 

 

\ .

3

1

 

 

1

pi:-nil;

pi

 

 

 

0

1

 

 

 

 

 

\ |

Значение, адрес которого нахо­ дится в pi, увеличивается на 2

Запись в pi константы «нулевой адрес»

I ^ 1

Операции отношения. Из всех возможных операций отношения допус­ каются только операции проверки равенства (=) и неравенства (< >). Эти операции проверяют соответственно равенство и неравенство адресов. На­ пример:

sign:=pl=p2; {переменная sign логического типа получает значение true или false в зависимости от значений указателей}

или

ifplonil

then ... {проверка адреса}

Поскольку в качестве базового типа типизированного указателя может быть использован любой тип, допустимо определять «указатель на указа­ тель». Например, если переменную ppi описать и инициализировать следую­ щим образом:

216

7. Программирование с использованием динамической памяти

ppi

\

\pi

\

\ i

1

Рис. 7.5. Указатель на указатель

integer и равно 1.

Const i:integer^ 1; Var pi: integer;

ppi: ""pi;

pi:=@i;

ppi:=@pi; ...

TO будет реализована схема, изображенная на рис. 7.5.

Для получения значения переменной i не­ обходимо дважды применить операцию разы­ менования. В нашем случае ppi^^ имеет тип

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

иупрощающие выполнение часто встречающихся операций.

1.Функция ADDR(x): pointer - возвращает адрес объекта х, в качестве которого может быть указано имя переменной, функции, процедуры. Выпол­ няет те же действия, что и операция «@».

2.Функция SEG(x): word - возвращает сегментный адрес указанного объекта.

3.Функция OFS(x): word- возвращает смещение указанного объекта.

4.Функция CSEG: word- возвращает текущее значение сегментного ре­ гистра CS - сегментный адрес сегмента кодов.

5.Функция DSEG: word- возвращает текущее значение сегментного ре­ гистра DS - сегментный адрес сегмента данных.

6.Функция PTR(seg,ofs:word):pointer - возвращает значение указателя по заданным сегментному адресу seg и смещению ofs.

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

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

Например:

Var L:longint; {длинное целое число}

Р1:^аггау[1..4] of byte; {указатель на область длиной 4 байта} k:byte;

217

Часть J. Основы алгоритмизации и процедурное программирова

Begin

1:423456789;

P1:=@L; {операция @ возвращает нетипизированный указатель} к:^Р1^[1]: {младший байт внутреннего представления числа L,

младший потому, что числа в памяти для данного типа компью­ теров хранятся с младшего байта}

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

7.2. Управление динамической памятью

Определяемые в примерах предьщущего параграфа указатели для на­ глядности содержали адреса статически размещенных переменных. Однако основное назначение указателей ~ адресация динамических переменных. Та­ кие переменные располагаются в свободной области, называемой динамиче­ ской памятью или «кучей». Эта область расположена после программы, и ее объем составляет около 200 ... 300 кБ, как это представлено на рис. 7.6. (GooTBeTCTBeHHO, чем больше объем программы, тем меньше размер свобод­ ной области памяти.) На этом рисунке также показаны значения стандартных переменных Borland Pascal, используемых для управления динамической об­ ластью:

HeapOrg - указатель на начало динамической области; HeapEnd - указатель на конец динамической области;

HeapPtr - указатель на те!0^щее значение границы свободной динамиче­ ской области.

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

1. Процедура New (Var <типизированный указатель>) - возвращает ад­ рес выделенного участка памяти через параметр-переменную. Размер участ­ ка памяти определяется базовым типом указателя.

Свободная память

Рис. 7.6. Размещение динамической области

218

7. Программирование с использованием динамической памяти

Например:

Varpi: ^integer; ...

New(pi); {теперь pi содержит адрес двух байт, выделенных из динамической памяти под размещение переменной целого типа}

2. Функция New (<^тип типизированного указателя>^;/7<?ш/^г~ возвраща­ ет адрес выделенного участка памяти. Размер участка памяти также опреде­ ляется базовым типом указателя.

Например:

Туре tpi: ^integer; Varpi:tpi; ...

pi:- New(tpi); {pi также содержит адрес двух байт, выделенных из динамической памяти под размещение переменной целого типа}

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

3. Процедура Dispose ('<типизированный указатель>^ - освобождает па­ мять по адресу, хранящемуся в указателе.

Например:

Dispose(pi);...

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

Серия последовательных обращений к New и Dispose обычно приводит к фрагментации памяти - память разбивается на небольшие фрагменты с чередованием свободных и занятых участков. В результате может возник­ нуть ситуация: свободной памяти для размещения новой переменной доста­ точно, но она не может быть размещена из-за отсутствия непрерывного уча­ стка требуемого размера.

Дпя уменьшения явления фрагментации используют специальные про­ цедуры.

4. Процедура Л/лгА (Varp:pointer)-запоминает значение HeapPtr в ука­ зателе р, полученном в качестве параметра.

5. UpoiXQjxypa. Release (Var p:pointer) - освобождает весь фрагмент памя­ ти, начиная с адреса р, зафиксированного в указателе процедуры Mark. На­ пример:

new(pl);

new(p2);

mark(p);

new(p3);

219

Часть J. Основы алгоритмизации и процедурное программирование

new(p4);

release(p);.,.

Примечание. Совместное использование процедур Dispose и Release недопустимо, так как Release разрушает список освобожденных фрагментов, создаваемый при выполнении Dispose.

Динамическую память можно выделять фрагментами, указывая их раз­

мер.

6.Процедура GetMem (Var p:pointer; size:word) - запрашивает у систе­ мы память размера, указанного в параметре size (запрашиваемый объем не должен превышать 64КБ), и помещает адрес выделенного системой фраг­ мента в переменную типа pointer с именем р. Как правило, данный способ выделения памяти используется, если требуется память под размещение бу­ феров, формат которых программисту не известен.

7.Функция SizeOf(x): word- возвращает длину указанного объекта х в байтах.

8.Процедура FreeMem (p:pointer; size:word) - освобождает область па­ мяти, вьщеленную процедурой GetMem.

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

Избежать подобной ситуации можно несколькими способами.

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

9. Функция Maxavail: longint ~ возвращает длину максимального непре­ рывного участка памяти.

10. Функция Memavail: longint - возвращает размер всей свободной па­ мяти - сумму длин всех свободных фрагментов.

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

Function HeapFuncfsize: word) : integer; far; begin HeapFunc: =7; end;

В программе необходимо определить адрес подпрограммы обработки ошибки НеарЕггог, указав собственную программу HeapFunc:

HeapError:=@HeapFunc; ...

Использование такой подпрограммы приведет к тому, что процедуры New и GetMem при исчерпании памяти вернут указатели, установленные в

220