SHPORA.net :: PDA

Login:
регистрация

Main
FAQ

гуманитарные науки
естественные науки
математические науки
технические науки
Search:
Title: | Body:

Динамическая память


Указатели. Работа с указателями.



Указатели позволяют работать с данными через их адреса.

В Turbo Pascalе имеется стандартный указательный тип Pointer и существует возможность определить пользовательский указательный тип для любого указанного типа.

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

type

PSomeType = ^ SomeType;



Пример:

Задача.

Определим тип записи TWorker (работник), содержащей поля ФИО, должность и зарплата. После чего определим указательный тип PWorker для типа TWorker, и, заодно, указательные типы PInteger для Integer и PChar для Char.



Решение:

Type

TWorker = record {запись Работник}

Fio:string[60]; {поле ФИО}

Post:string[60]; {поле Должность}

Wages:Integer; {поле Заработная плата}

end;



PWorker = ^ TWorker; {указатель на TWorker}

PInteger = ^ Integer; {указатель на Integer}

PChar = ^ Char; {указатель на Char}



Несложно заметить, что все указательные типы в данном примере начинаются с буквы P. Буква P означает, что данный тип - указательный (P от Pointer). Указательный тип не обязательно должен начинаться на букву P, его можно обзывать любым другим именем, но лучше все-таки придерживаться именно такого правила - имя любого указательного типа начинать с буквы P - в этом случае создаваемая программа будет легче читаться и пониматься (а следовательно и легче отлаживаться).







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





1) Используя операцию взятия адреса @.



Пример:

Объявим несколько переменных указательного типа и несколько переменных базовых типов (базовым типом по отношению к указательному типу называется тот тип от которого произведен указательный, например базовым для типа PWorker является тип TWorker, а базовым типом для PChar - Char).



Var

{указатели}

pw:PWorker;

pi:PInteger;

pc: PChar;

p : Pointer;



{переменные базовых типов}

w:TWorker;

i:Integer;

c: Char;



Заметим, что все указатели в Turbo Pascalе занимают 4 байта памяти. После несложных вычислений выясним, что одна переменная типа TWorker занимает 124 байта памяти. Вспомним, что переменные типа Integer и Char занимают соответственно 2 и 1 байт.

Зная размеры каждой переменной, и зная адрес первой из объявленных переменных, можно вычислить адреса оперативной памяти по которым расположены все остальные объявленные переменные. Дело в том, что компилятор располагает все объявленные переменные друг за другом, сначала первую, затем вторую и так далее. Пусть адрес первой из объявленных переменных - переменной pw равен $561F005E (значение взято реальное).

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



имя переменной длина переменной адрес

pw 4 $561F005E

pi 4 $561F0062

pc 4 $561F0066

p 4 $561F006A

w 124 (127=$7C) $561F006E

i 2 $561F00EA

c 1 $561F00EC



Теперь зная адреса переменных можно продемонстрировать получение этих самых адресов:



begin

p:=@p; {в переменную p заноситься адрес переменной p - т.е. число $561F006A}

p:=@c; {в переменную p заноситься адрес переменной c - т.е. число $561F00EC}

pc:=@c; {в переменную pc заноситься адрес переменной c - т.е. число $561F00EC}

pi:=@i; {в переменную pi заноситься адрес переменной i - т.е. число $561F00EA}

pw:=@w; {в переменную pw заноситься адрес переменной w - т.е. число $561F006E}

p:=@pw; {в переменную p заноситься адрес переменной pw - т.е. число $561F005E}

end.





2) Присвоение элементу указательного типа значения другого элемента указательного типа:



2.1) Присваивание элементу типа Pointer значение другого элемента любого

указательного типа



Пример:



begin

pw:=@w; {в переменную pw заноситься адрес переменной w - т.е. число $561F006E}

pc:=@c; {в переменную pc заноситься адрес переменной c - т.е. число $561F00EC}

p:=pw; {в переменную p заноситься адрес переменной w - т.е. число $561F006E}

p:=pc; {в переменную p заноситься адрес переменной c - т.е. число $561F00EC}

end.



2.2) Присваивание элементу любого указательного типа значение элемента типа Pointer



Пример:



begin

p:=@w; {в переменную p заноситься адрес переменной w - т.е. число $561F006E}

pw:=p; {в переменную pw заноситься адрес переменной w - т.е. число $561F006E}

pc:=p; {в переменную pc заноситься адрес переменной w - т.е. число $561F006E}

end.



2.3) Присваивание элементу любого указательного типа значение элемента того же указательного типа



Пример:

var

i:integer;

c:char;

pc1,pc2:PChar;

pi1,pi2: PInteger;

begin

pc1:=@c; {в переменную pc1 заноситься адрес переменной c}

pc2:=pc1; {в переменную pc2 заноситься адрес переменной c}

pi2:=@i; {в переменную pi2 заноситься адрес переменной i}

pi1:=pi2; {в переменную pi1 заноситься адрес переменной i}

end.







3) Присвоение элементу любого указательного типа значения nil

Замечание: nil - это специальное значение указателя - "указатель в никуда". Определен он следующим образом:



const

nil : longint = 0;



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

Пример:



var

p:Pointer;

pi: PInteger;

pc:PChar;

begin

p:=nil;

pi:=nil;

pc:=nil;

end.





4) Значение указателя можно задавать процедурами New и Getmem.



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

Кроме такого - статического - способа выделения памяти, существует и другой способ - динамический. При динамическом способе выделения памяти в программе объявляют не сами переменные, а только указатели на них, а память для размещения переменных выделяют при помощи процедур New и Getmem.



Процедура New:



procedure New(var P:Pointer) - создает новую динамическую переменную и заносит в P адрес созданной переменной.



Пример 1:

var

pi: Integer; {указатель на динамически создаваемую переменную}

begin

New(pi); {создали динамическую переменную целого типа}

pi^:=100; {присвоили ей значение 100}

writeln(pi^); {вывели на экран ее значение =100}

Dispose(pi); {уничтожили созданную динамическую переменную}

end.



Пример 2:

var

pw: PWorker; {указатель на динамически создаваемую переменную}

begin

New(pw); {создали динамическую переменную типа TWorker}

pw^.Fio:='Иванов'; {присвоили полю Fio строку 'Иванов'}

pw^.Post:='директор'; {присвоили полю Post строку 'директор'}

pw^.Wages:=2500; {присвоили полю Wages значение 2500}

writeln(pw^.Fio); {вывели на экран строку 'Иванов'}

writeln(pw^.Post); {вывели на экран строку 'директор'}

writeln(pw^.Wages); {вывели на экран значение 2500}

Dispose(pw); {уничтожили созданную динамическую переменную}

end.



Замечание 1:

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

Замечание 2:

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

Замечание 3:

Ни в коем случае нельзя обращаться к памяти по адресу хранящемся в неиницилизированном указателе (т.е. в указателе, которому не присвоили значение адреса какой-то конкретной переменной).

Пример:

var

pi:PInteger;

i:integer;

begin

pi^:=100; {Грубая ошибка, весьма часто приводящая к зависанию компьютера}

{Дело в том, что неинициализированный указатель pi все равно хранит

какое-то значение, которое при выполнении оператора pi^:=100;

интерпретируется как адрес переменной целого типа. Именно по

этому НЕОПРЕДЕЛЕННОМУ адресу и будет записано число 100.

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

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

к зависанию вашей программы или даже компьютера, а в некоторых

случаях никакими "спецэффектами" выдавать себя не будет.

Чем очень сильно затруднит поиск ошибки}



pi:=@i;

pi^:=200; {А вот так можно. В этом случае число 200 будет занесено

в переменную i}



New(pi);

pi^:=300; {И так можно. В этом случае число 300 будет занесено в динамически

созданную переменную целого типа, адрес которой храниться в pi}



Dispose(pi); {Нельзя забывать уничтожать динамически создаваемые переменные

после окончания их использования.

При создании динамической переменной под ее размещение

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

общий объем памяти не бесконечен, то становиться понятным,

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

рано или поздно кончится.

Чтобы этого не случилось, память расходуют экономно, уничтожая

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

ненужными.

При уничтожении динамически созданной переменной, память

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

использована для размещения других динамических переменных}

end.



Замечание 4:

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

Пример:

var

pi:PInteger;

begin

New(pi); {создали динамическую переменную}

pi^:=10; {присвоили ей значение 10}

writeln('pi^=', pi^ ); {вывели на экран 10}

Dispose(pi); {уничтожили динамическую переменную}

pi^:=20; {ОШИБКА!!! Динамической переменной уже не существует,

а мы записываем в нее 20}

end.





Процедура GetMem:



procedure GetMem(var P:Pointer; Size:Word) - создает новую динамическую переменную заданного размера Size, и помещает ее адрес в указатель P.



Пример:

var

pi: Integer; {указатель на динамически создаваемую переменную}

begin

GetMem(pi,sizeof(integer)); {создали динамическую переменную целого типа}

pi^:=1000; {присвоили ей значение 1000}

writeln(pi^); {вывели на экран ее значение =1000}

FreeMem(pi,sizeof(integer)); {уничтожили созданную динамическую переменную}

end.





Замечание:

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



Процедура FreeMem:



procedure FreeMem(var P:Pointer; Size:Word) - уничтожает динамическую переменную заданного размера Size.









Последнее, что стоит упоминуть о работе с указателями, это то, что к указателям можно применить две операции сравнения: = и <>.Заметим, что сравнивать можно указатели следующих типов:

1) pointer с pointer

2) pointer с любым указательным типом

3) любой указательный тип с pointer

4) любой указательный тип с тем же самым указательным типом



Пример:



var

p,p1:pointer;

pi,pi1:PInteger;

pc,pc1: PChar;

pw,pw1:PWorker;

b: Boolean;

begin

...

if p = p1 ... {pointer и pointer - сравнивать можно}

while p <> pi ... {pointer и PInteger - сравнивать можно}

while pi = pi1 ... {PInteger и PInteger - сравнивать можно}

b:= pc = p; ... {PInteger и pointer - сравнивать можно}

until pw <> pw1 ... {PWorker и PWorker - сравнивать можно}

until pc = pi ... {PChar и PInteger - сравнивать НЕЛЬЗЯ!!!}

if pw <> pi1 ... {PWorker и PInteger - сравнивать НЕЛЬЗЯ!!!}

...

end.









Работа с динамической памятью



Динамическая память - эта та часть оперативной памяти компьютера, в которой располагаются динамически создаваемые переменные. В Turbo Pascalе динамическая память имеет размер порядка 300-500 Кб. При этом стоит отметить, что любая переменная, как статическая так и динамическая не может превышать размер 64 Кб.



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





Пример:

Задача. Ввести массив А целых чисел . Сформировать массив В из элементов массива А имеющих четное значение.



Решение:

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





Program DynamicMemoryExample; Program StaticMemoryExample;

const const

maxN = 100; {максимально возможное maxN = 100;

количество элементов

в массивах A и B}

type type

Arr = array[1..maxN] of integer;{массив} Arr = array[1..maxN] of integer;

PArr = ^Arr; {указатель на массив}

var var

a,b:PArr; {a,b -массивы} a,b:Arr;

n,m:integer; {n,m - кол-во элементов} n,m:integer;

i:integer; {i - счетчик} i:integer;

begin begin



{Ввод n}

repeat repeat

read(n); read(n);

until (n>=1) and (n<=maxN); until (n>=1) and (n<=maxN);



{Выделение памяти под массив A}

GetMem(a,sizeof(integer)*n);



{Ввод массива A}

for i:=1 to n do for i:=1 to n do

read(a^[i]); read(a[i]);



{Подсчет четных элементов в массиве A}

m:=0;

for i:=1 to n do

if a^[i] mod 2 = 0

then inc(m);



{Выделение памяти под массив B}

GetMem(b,sizeof(integer)*m);



{Заполнение массива B четными

элементами массива A}

m:=0; m:=0;

for i:=1 to n do for i:=1 to n do

if a^[i] mod 2 = 0 if a[i] mod 2 = 0

then then

begin begin

inc(m); inc(m);

b^[m]:=a^[i]; b[m]:=a[i];

end; end;



{Вывод массива B}

for i:=1 to m do for i:=1 to m do

write(b^[i],' '); write(b[i],' ');



{Уничтожение массивов A и B -

освобождение занятой динамической

памяти}

Freemem(a,sizeof(integer)*n);

Freemem(b,sizeof(integer)*m);

end. end.



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



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

На самом деле это далеко не так.

Если проанализировать наш пример, то мы увидим, что в решении с использованием динамической памяти, памяти под массивы A и B выделяется РОВНО СТОЛЬКО, СКОЛЬКО НЕОБХОДИМО ДЛЯ РАБОТЫ программы. В решении же без динамической памяти массивы A и B в любом случае занимают по 200 байт каждый.



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

1. Использование динамической памяти в программах приводит к уменьшению объема используемой памяти - это плюс.

2. Использование динамической памяти ведет к увеличению размера программы - это минус.

3. Использование динамической памяти чревато множеством сложнообнаруживаемых ошибок связанных с работой с указателями - это минус.

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



Замечание:

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





В заключении описания работы с динамической памятью перечислим типичные ошибки:



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



var

P:Pointer;

begin

GetMem(P,100); {выделили 100 байт}

GetMem(P,200); {выделили еще 200 байт}

FreeMem(P,200); {освободили 200 байт - 100 байт освободить уже не удастся... - ОШИБКА}

end.



2) Многократное освобождение одной и той же динамической памяти.

var

pw:PWorker;

begin

New(pw); {выделили память под переменную типа TWorker}

Dispose(pw); {освободили память из-под переменной типа TWorker}

Dispose(pw); {попытались еще раз освободить эту же память}

end.



3) Память не выделялась, но освобождается.

var

PL:^LongInt;

L:LongInt;

begin

PL:=@L; {PL указывает на переменную L}

L:=100000;

writeln(PL^); {выводится число 100000}

Dispose(PL); {пытаемся освободить память, которую не выделяли - ОШИБКА}

end.