Назад | Начало урока | Вперед
Содержание

Глава 8

Для чего нужны указатели

Наиболее часто указатели применяются в следующих случаях:

Вверх

Стек и динамически распределяемая память

В уроке 5 (Функции) уже упоминались основные области памяти:

Локальные переменные и параметры функций размещаются в стеке.
Объектный код программ размещается в сегментах, а глобальные переменные-
в области глобальных переменных. Регистры используются для внутренних
целей процессора, например для контроля вершины стека и указателя команд.
Остальная часть памяти составляет так называемую свободную память,
или динамически распределяемую память или адресную память или кучу(heap).

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

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

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

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

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

Вверх

Оператор new

Для выделения необходимого участка памяти в динамически распределяемой
памяти используется ключевое слово new. После слова new следует указать
тип объекта, который будет размещен в памяти. Это необходимо чтобы
компилятор мог определить размер области памяти необходимой для
размещения объекта. Например выражение new unsigned short int
выделит два байта динамической памяти.


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

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

unsigned short int*pPointer;//объявлен указатель
pPointer = new unsigned short int;//указатель на динамическую память

(здесь оператор new вернул адрес выделенного фрагмента памяти
в области динамической памяти и присвоил его указателю pPointer)

Безусловно инициализировать указатель можно сразу в момент его создания:

unsigned short int*pPointer = new unsigned short int;

В любом случае pPointer указывает теперь на область памяти в которой
зарезервировано место для переменной типа unsigned short int,
это место размещено в динамически распределяемой памяти компьютера.
(а ранее он указывал на место в стеке).
Такой указатель можно использовать как любой другой указатель на
переменную и передавать с его помощью значения в эту область
памяти. Например:

*pPointer = 72;

Это значит:разместить число 72 в той области динамически распределяемой
памяти на которую указывает pPointer.

Примечание:

Если оператор new не сможет выделить место в динамически распределяемой
памяти(в конце концов - память ограниченный ресурс), то он передаст
исключение. Более подробно информация на эту тему в главе 20
"Исключения и обработка ошибок".

Вверх

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

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

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

delete pPointer;

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

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

Предупреждение:

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

Animal *pDog = new Animal;
delete pDog;
pDog = 0;

delete pDog; //вполне безопасно!

Вверх

Создание использование и удаление указателей
// Listing 8.4
// Allocating and deleting a pointer

#include <iostream.h> int main()

// using std::cout;

int localVariable = 5;
int * pLocal = &localVariable;

int * pHeap = new int;
*pHeap = 7;

cout << "localVariable: " << localVariable << "\n";
cout << "*pLocal: " << *pLocal << "\n";

cout << "*pHeap: " << *pHeap << "\n";
delete pHeap;

pHeap = new int;
*pHeap = 9;
cout << "*pHeap: " << *pHeap << "\n";
delete pHeap;
return 0;


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

Анализ:

Сначала объявляется переменная , ей присваивается значение 5.
Затем объявляется указатель на тип int и ему присваивается адрес
ранее объявленной и инициализированной переменной localVariable.

int localVariable = 5;
int * pLocal= &localVariable;

Далее объявляется другой указатель на переменную типа int и затем
этот указатель инициализируется результатом операции new int.
Которая говорит что указатель теперь указывает на динамически
распределяемую память.
В результате в динамически распределяемой памяти выделяется пространство
для переменной типа int. В следующей строке этому участку памяти
присваивается значение 7.(или другими словами в этот участок памяти
заносится значение 7)

int * pHeap = new int;
*pHeap = 7;

Далее выводится на экран значение локальной переменной.(находится в стеке)
В следующей строке выводится значение на которое указывает указатель
(так же в стеке)(Значит указатель указывает на значение которое хранится
в стеке.)

cout << "localVariable: " << localVariable << "\n";
cout << "*pLocal: " << *pLocal << "\n";

Как и ожидалось они одинаковы.

Далее на экран выводится значение указанное в pHeap. Это доказывает что
значение хранящееся в динамически распределяемой памяти вполне доступно.

cout << "*pHeap: " << *pHeap << "\n";

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

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

delete pHeap;

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

pHeap = new int;

Теперь указатель указывает на новое место в динамической памяти
зарезервированное под переменную типа int.
Теперь занесем в это место целочисленное значение

*pHeap = 9;

Затем выведем это значение из динамической памяти на экран

cout << "*pHeap: " << *pHeap << "\n";

Теперь вновь освободим этот участок памяти и сам указатель.

delete pHeap;

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

Вверх

Утечка памяти

Посмотрим пример:

unsigned short int*pPointer = new unsigned short int;
pPointer = 72;
pPointer = new unsigned short int;
pPointer = 84;

Как видим сначала объявляется указатель и выделяется память
для хранения переменной типа unsigned short int в динамической памяти.
В следующей строке в выделенную область записывается значение 72.
Затем указателю присваивается адрес другой области памяти,
в которую записывается число 84. Теперь исходный участок памяти,
содержащий значение 72 оказывается недоступен, поскольку указателю
на эту область было присвоено новое значение.

Правильно этот фрагмент должен выглядеть так:

unsigned short int*pPointer = new unsigned short int;
pPointer = 72;
delete pPointer;
pPointer = new unsigned short int;
pPointer = 84;

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


Примечание

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


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


Назад | Начало урока | Вверх | Вперед
Содержание

Hosted by uCoz