Методические указания для студентов 1 курса факультета математики, механики и компьютерных наук
Вид материала | Методические указания |
Содержание1.6Процедуры GetMem и FreeMem 1.7Ошибки при работе с динамической памятью |
- Программа курса «история и методология математики» для студентов дневного отделения, 151.46kb.
- Методические указания курса «культурология» Для студентов биологического факультета, 331.04kb.
- Войта Елена Александровна, магистрант факультета математики, механики и компьютерных, 129.35kb.
- Методические указания по изучению дисциплины Для студентов 4 курса заочного факультета, 3497.6kb.
- Методические указания и контрольные задания по английскому языку для студентов II курса, 375.13kb.
- И. И. Мечникова Институт математики, экономики и механики Кафедра математического обеспечения, 900.66kb.
- Методические указания к изучению курса «История мифологии» для студентов 1 курса факультета, 420.44kb.
- Методические указания к выполнению курсовой работы по дисциплине «Основы научных исследований», 403.99kb.
- Отчет по самообследованию дополнительной профессиональной программы для получения дополнительной, 317.2kb.
- Методические указания к выполнению курсовой работы по дисциплине «Оценка качества продовольственного, 856.1kb.
1.6Процедуры GetMem и FreeMem
Для выделения/освобождения динамической памяти, контролируемой бестиповым указателем, используется другая пара процедур: GetMem и FreeMem. Если p – указатель любого типа (в частности, типа pointer), то вызов GetMem(p,nb) выделяет в динамической памяти участок размера nb байтов и записывает адрес его начала в указатель p. Вызов FreeMem(p) освобождает динамическую память, контролируемую указателем p. Следует обратить внимание, что при вызове FreeMem не указывается размер освобождаемой памяти, поскольку в каждом выделенном блоке хранится его размер, и FreeMem пользуется этой информацией.
В большинстве ситуаций использования типизированных указателей и процедур New и Dispose оказывается достаточно. Процедуры GetMem и FreeMem применяются там, где требуется более гибкое управление памятью.
Пример. Динамический массив.
Динамическим будем называть массив, размер которого задается в процессе работы программы. В Delphi (начиная с версии 4) динамические массивы реализованы средствами языка:
var dyn: array of integer;
n: integer;
begin
read(n);
Assert(n>0);
SetLength(dyn,n);
dyn[0]:=5;
...
Однако, динамические массивы нетрудно создать и с помощью обычных массивов с помощью процедур GetMem и FreeMem:
const sz=MaxInt div sizeof(integer);
type Arr: array [0..sz-1] of integer;
var dyn: Arr;
n: integer;
begin
read(n);
Assert(n>0);
GetMem(dyn,n*sizeof(integer));
dyn[0]:=5; // можно dyn[0]:=5
...
Идея подобной реализации динамического массива состоит в следующем. Описывается тип массива с большим количеством элементов и переменная dyn, являющаяся указателем на этот тип. С помощью GetMem выделяется нужное количество памяти, определяемое в процессе работы программы; адрес выделенной памяти записывается в переменную dyn. С этого момента можно обращаться к элементам массива, используя запись вида dyn[0]. Операцию разыменования в Delphi можно опускать, поэтому с dyn можно обращаться как с обычным массивом: dyn[0]. В конце работы с таким массивом следует вызвать FreeMem(dyn).
Отметим, что при включенном режиме проверки выхода за границы диапазона {$R+} нельзя выделять под массив память, превосходящую его размер, то есть должно выполняться условие n<=sz. Поэтому следует задавать размер массива sz максимально возможным. В Delphi память, занимаемая переменной любого типа, не должна превосходить 2 Гб, т.е. MaxInt байт. Поскольку элементы массива имеют тип integer, то в качестве sz выбрано максимально возможное значение MaxInt div sizeof(integer).
1.7Ошибки при работе с динамической памятью
Как было отмечено, при работе с динамической памятью можно совершить большое количество ошибок, которые имеют различные последствия и различную степень тяжести. Большинство этих ошибок проявляется не сразу, а через некоторое время в процессе выполнения программы. Следовательно, такие ошибки труднонаходимы и потому особенно опасны. Используя принцип «предупрежден – значит, вооружен», перечислим наиболее часто встречающиеся варианты ошибок при работе с динамической памятью.
1. Попытка воспользоваться неинициализированным указателем.
var pi: integer;
i: integer;
begin
pi:=5;
Если pi – глобальная переменная, то она автоматически инициализируется нулевым значением, т.е. имеет значение nil. Разыменование нулевого указателя приводит к ошибке времени выполнения. Если pi – локальная переменная, то она по умолчанию не инициализируется, а поэтому содержит непредсказуемое значение. Это значение трактуется как адрес целой переменной, к которой осуществляется доступ. Как правило, в этой ситуации возникает исключение Access Violation (нарушение защиты доступа), но по чистой случайности может оказаться, что указатель pi содержит истинный адрес переменной программы, тогда переменная будет изменена, выполнение программы продолжится дальше, а факт изменения переменной непредсказуемым образом повлияет на дальнейшее выполнение программы.
2. «Висячие» указатели.
После освобождения динамической памяти указатель продолжает указывать на старое место. Такие указатели называются «висячими». Попытка записи по такому указателю не приводит к немедленной ошибке. Однако память, на которую он указывает, могла быть уже выделена другой динамической переменной, и попытка записи приведет к порче этой переменной.
var pi: integer;
begin
New(pi);
pi:=5;
Dispose(pi); // указатель становится "висячим"
...
pi:=6; // ошибка!
Если после Dispose(pi) сразу написать pi:=nil, то в дальнейшем при попытке разыменовать нулевой указатель pi возникнет исключение, что является более предпочтительным, чем скрытая ошибка изменения другой переменной. Данный прием следует взять на вооружение и после освобождения динамической переменной обнулять указатель:
Dispose(pi);
pi:=nil;
3. «Утечка» памяти.
Данная ошибка возникает, когда память не освобождается, но перестает контролироваться указателем. Подобную ошибку называют «утечкой» памяти, поскольку такую память невозможно освободить. Такая ошибка труднонаходима, поскольку практически не сказывается на работе приложения. Однако при систематических утечках программа требует все больше памяти у операционной системы, замедляя работу других приложений. Далее приводятся две распространенные ситуации, в которых возникает утечка памяти.
Пример 1. Повторное выделение памяти.
Если выделить память повторно для того же указателя, то ранее выделенная память «утечет»:
var pi: integer;
begin
New(pi);
pi:=5;
New(pi);
Пример 2. Выделение памяти под локальную переменную без освобождения.
procedure pp;
var pi: integer;
begin
New(pi);
end;
Данная процедура составлена ошибочно: локальный указатель pi уничтожается после завершения работы процедуры, поэтому контролируемая им динамическая память «утекает». Особенно опасна подобная утечка при вызове такой процедуры в цикле:
for i:=1 to MaxInt do
pp;
4. Память, выделенная динамически для глобальных переменных-указателей, не возвращается явно в конце программы.
Поскольку динамическая память автоматически освобождается в конце работы программы, отсутствие явного вызова Dispose или FreeMem для глобальных переменных-указателей на динамическую память не может считаться ошибкой и свидетельствует просто о неаккуратности программирования. Однако при переносе текста основной программы в процедуру мы получим ошибку утечки памяти, описанную в предыдущем пункте.
5. Попытка освободить динамическую память, не выделенную ранее.
var pi: integer;
begin
Dispose(pi);
Подобная ошибка приводит к немедленной генерации исключения, поэтому не принадлежит к числу опасных. Заметим, что в Delphi вызов процедуры Dispose для нулевого указателя просто игнорируется, не приводя к генерации исключения.
6. Попытка дважды освободить занимаемую память.
var pi: integer;
begin
New(pi);
...
Dispose(pi);
Dispose(pi);
При повторном вызове процедуры Dispose будет сгенерировано исключение.
7. Попытка освободить нединамическую память.
var pi: integer;
i: integer;
begin
pi:=@i;
Dispose(pi);
При вызове Dispose для нединамической переменной будет сгенерировано исключение.
8. Выход за память, выделенную процедурой GetMem.
Поскольку GetMem выделяет количество памяти, не зависящее от размера объекта, с которым связан указатель, она менее безопасна, чем New. Например, при реализации с помощью GetMem динамического массива можно предпринять попытку обратиться за границы выделенной памяти:
GetMem(dyn,n*sizeof(integer));
dyn[n+1]:=5;
Обычно такая ошибка приводит к исключению Access Violation.