Глава 8
Наиболее часто указатели применяются в следующих случаях:
Вверх
В уроке 5 (Функции) уже упоминались основные области памяти:
Локальные переменные и параметры функций размещаются в стеке.
Особенностью локальных переменных является то, что после выхода из функции,
Глобальные переменные позволяют частично решить эту проблему ценой
Динамически распределяемую память можно представить как огромный массив
Когда функция возвращает значение, стек очищается автоматически.
Важным преимуществом динамической памяти является то, что выделенная
Вверх
Для выделения необходимого участка памяти в динамически распределяемой
(здесь оператор new вернул адрес выделенного фрагмента памяти
Безусловно инициализировать указатель можно сразу в момент его создания:
В любом случае pPointer указывает теперь на область памяти в которой
Это значит:разместить число 72 в той области динамически распределяемой
Если оператор new не сможет выделить место в динамически распределяемой
Вверх
Поскольку память, распределенная с помощью указателя new , не
Чтобы освободить выделенную память, используется ключевое слово delete,
delete pPointer;
При удалении указателя с помощью оператора delete происходит реальное
Листинг 8.4 демонстрирует размещение переменной в динамической памяти,
Когда оператор delete применяется к указателю, происходит освобождение
delete pDog; //вполне безопасно!
Вверх
// using std::cout;
int localVariable = 5;
int * pHeap = new int;
cout << "localVariable: " << localVariable << "\n";
cout << "*pHeap: " << *pHeap << "\n";
pHeap = new int;
Интересно что к созданной в динамической памяти переменной
Анализ:
Сначала объявляется переменная , ей присваивается значение 5.
Далее объявляется другой указатель на переменную типа int и затем
Далее выводится на экран значение локальной переменной.(находится в стеке)
Как и ожидалось они одинаковы.
Далее на экран выводится значение указанное в pHeap. Это доказывает что
Значение переменной хранящейся в динамической памяти можно получить только
В следующей строке оператор delete освобождает участок динамически
В следующей строке указатель будет указывать на другой участок памяти
Теперь указатель указывает на новое место в динамической памяти
Затем выведем это значение из динамической памяти на экран
Теперь вновь освободим этот участок памяти и сам указатель.
Поскольку программа на этом заканчивается то последний оператор
Вверх
Посмотрим пример:
Как видим сначала объявляется указатель и выделяется память
Правильно этот фрагмент должен выглядеть так:
Теперь выделенная под переменную память освобождается корректно.
Каждый раз когда в программе используется оператор new,
Назад |
Начало урока |
Вверх |
Вперед
Для чего нужны указатели
Стек и динамически распределяемая память
Объектный код программ размещается в сегментах, а глобальные переменные-
в области глобальных переменных. Регистры используются для внутренних
целей процессора, например для контроля вершины стека и указателя команд.
Остальная часть памяти составляет так называемую свободную память,
или динамически распределяемую память или адресную память или кучу(heap).
в которой они были объявлены, память выделенная для их
хранения, освобождается, а значения переменных уничтожаются.
неограниченного доступа к ним из любой точки программы, что значительно
усложняет восприятие текста программы.
Использование динамической памяти полностью решает обе проблемы.
последовательно пронумерованных ячеек, предназначенных для хранения
данных. В отличие от стека, ячейкам свободной памяти нельзя присвоить
имя. Но можно зарезервировав определенное количество ячеек, запомнить
адрес первой из них в указателе. Заметьте что помнить адрес переменной
не обязательно-достаточно лишь записать его значение в указатель.
Указатель позволяет обращаться к данным забыв о подробностях.
Когда область действия локальных переменных заканчивается, они так же
удаляются из стека. Но динамическая память не очищается до завершения
самой программы. Поэтому ответственность за освобождение всей памяти,
зарезервированной под все используемые данные, ложится на программиста.
в ней область не может использоваться в других целях до тех пор,
пока не будет освобождена явно. Поэтому если во время работы функции
в динамической памяти выделяется область, ее можно использовать даже
по завершении работы функции.
Еще одним преимуществом динамически распределяемой памяти по сравнению с
глобальными переменными является то , что доступ к данным можно получить
только из тех функций, которые обладают доступом к указателю, хранящему
нужный адрес.
Это позволяет жестко контролировать характер манипулирования данными,
а так же избегать нежелательного или случайного их изменения.
Для этого необходимо создать указатель на распределяемую область
динамической памяти и передать его соответствующей функции.
Далее описано как это сделать
Оператор new
памяти используется ключевое слово new. После слова new следует указать
тип объекта, который будет размещен в памяти. Это необходимо чтобы
компилятор мог определить размер области памяти необходимой для
размещения объекта. Например выражение new unsigned short int
выделит два байта динамической памяти.
фрагмента памяти, который должен быть присвоен указателю.
Например чтобы
создать в динамически распределяемой памяти переменную типа unsigned
short, необходимо записать:
unsigned short int*pPointer;//объявлен указатель
pPointer = new unsigned short int;//указатель на динамическую память
в области динамической памяти и присвоил его указателю pPointer)
unsigned short int*pPointer = new unsigned short int;
зарезервировано место для переменной типа unsigned short int,
это место размещено в динамически распределяемой памяти компьютера.
(а ранее он указывал на место в стеке).
Такой указатель можно использовать как любой другой указатель на
переменную и передавать с его помощью значения в эту область
памяти. Например:
*pPointer = 72;
памяти на которую указывает pPointer.
памяти(в конце концов - память ограниченный ресурс), то он передаст
исключение. Более подробно информация на эту тему в главе 20
"Исключения и обработка ошибок".
Оператор delete
По завершении работы с выделенной областью памяти ее следует
освободить. Делается это с помощью оператора delete, после которого
записывается имя указателя. Оператор delete освобождает область
памяти, на которую указывает указатель. Помните, что сам указатель в
отличие от области памяти, на которую он указывает, является локальной
переменной! Поэтому когда функция, объявившая указатель, завершает
работу, он выходит из области действия, а содержащийся в нем адрес теряется.
освобождается автоматически, то в случае потери ее адреса не удастся
ни удалить, ни использовать ее. Такой участок памяти становится абсолютно
недоступным, а подобная ситуация называется утечкой памяти. Это
название очень точно отражает сложившуюся ситуацию, посколько
блокированные участки памяти не могут быть восстановлены до
завершения программы, и если такое случится в каком-либо цикле,
то свободная память компьютера утекает, как вода в дыру (обычно до конца).
например:
освобождение участка памяти адрес которого находится в указателе.
Но сам указатель остается (ведь это обычная переменная), и ему
может быть передан на хранение другой адрес.
ее использование и удаление.
области динамической памяти, на которую этот указатель ссылается.
Повторное применение оператора delete к этому же указателю приведет
к зависанию программы. Рекомендуется при освобождении области
динамической памяти присваивать связанному с ней указателю нулевое
значение (0). Вызов оператора delete для нулевого указателя пройдет
совершенно безболезненно для программы. Например:
Animal *pDog = new Animal;
delete pDog;
pDog = 0;
Создание использование и удаление указателей
// Listing 8.4
// Allocating and deleting a pointer
#include <iostream.h>
int main()
int * pLocal = &localVariable;
*pHeap = 7;
cout << "*pLocal: " << *pLocal << "\n";
delete pHeap;
*pHeap = 9;
cout << "*pHeap: " << *pHeap << "\n";
delete pHeap;
return 0;
нельзя обратиться напрямую-у нее нет имени, а только через
указатель на нее.
Затем объявляется указатель на тип int и ему присваивается адрес
ранее объявленной и инициализированной переменной localVariable.
int localVariable = 5;
int * pLocal= &localVariable;
этот указатель инициализируется результатом операции new int.
Которая говорит что указатель теперь указывает на динамически
распределяемую память.
В результате в динамически распределяемой памяти выделяется пространство
для переменной типа int. В следующей строке этому участку памяти
присваивается значение 7.(или другими словами в этот участок памяти
заносится значение 7)
int * pHeap = new int;
*pHeap = 7;
В следующей строке выводится значение на которое указывает указатель
(так же в стеке)(Значит указатель указывает на значение которое хранится
в стеке.)
cout << "localVariable: " << localVariable << "\n";
cout << "*pLocal: " << *pLocal << "\n";
значение хранящееся в динамически распределяемой памяти вполне доступно.
cout << "*pHeap: " << *pHeap << "\n";
через указатель. Обращение напрямую невозможно-у переменной нет имени!
распределяемой памяти. Это не только освобождает память , но и ликвидирует
связь указателя с этим участком. Теперь указатель 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;
за ним должен следовать оператор delete.
Очень важно отслеживать указатели, ссылающиеся на выделенные области
динамической памяти, и вовремя освобождать ее.
Обрати внимание что после delete сначала указателю присваивается
новое место в динамической памяти и лишь затем оно инициализируется
значением необходимого формата.
Содержание