Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лекции по Программированию.doc
Скачиваний:
49
Добавлен:
11.02.2015
Размер:
1.22 Mб
Скачать

Лекция 8. Динамические объекты

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

1. Выделение памяти в соответствие с типом указателя

Оператор newсостоит их ключевого словаnew, за которым следует спецификатор типа. Этот спецификатор может относиться к встроенным типам или к типам классов. Например:

new int;

размещает в памяти один объект типа int.

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

int *pi = new int;

Здесь оператор newсоздает один объект типаint, на который ссылается указательpi. Выделение памяти из хипа во время выполнения программы называетсядинамическимвыделением. Мы говорим, что память, адресуемая указателем pi, выделена динамически.

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

if ( *pi == 0 )

вероятно, даст false, поскольку объект, на который указываетpi, содержит случайную последовательность битов. Следовательно, объекты, создаваемые с помощью оператораnew, рекомендуется инициализировать. Программист может инициализировать объект типаintиз предыдущего примера следующим образом:

int *pi = new int( 0 );

Константа в скобках задает начальное значение для создаваемого объекта; теперь piссылается на объект типаint, имеющий значение0. Выражение в скобках называетсяинициализатором. Это может быть любое выражение (не обязательно константа), возвращающее значение, приводимое к типуint.

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

int ival = 0; // создаем объект типа int и инициализируем его 0

int *pi = &ival; // указатель ссылается на этот объект

не считая, конечно, того, что объект, адресуемый pi, создается библиотечной функциейnew()и размещается в хипе.

Описанные операторы newмогут вызывать одну проблему: хип, к сожалению, является конечным ресурсом, и в некоторой точке выполнения программы мы можем исчерпать его. Если функцияnew()не может выделить затребованного количества памяти, она возбуждает исключениеbad_alloc.

Время жизни объекта, на который указывает pi, заканчивается при освобождении памяти, где этот объект размещен. Это происходит, когда pi передается операторуdelete. Например,

delete pi;

освобождает память, на которую ссылается pi, завершая время жизни объекта типаint. Программист управляет окончанием жизни объекта, используя операторdeleteв нужном месте программы. Этот оператор вызывает библиотечную функциюdelete(), которая возвращает выделенную память в хип. Поскольку хип конечен, очень важно возвращать ее своевременно.

Глядя на предыдущий пример, вы можете спросить: а что случится, если значение piпо какой-либо причине было нулевым? Не следует ли переписать этот код таким образом:

// необходимо ли это?

if ( pi != 0 )

delete pi;

Нет. Язык С++ гарантирует, что оператор deleteне будет вызывать функциюdelete()в случае нулевого операнда. Следовательно, проверка на0необязательна. (Если вы явно добавите такую проверку, в большинстве реализаций она фактически будет выполнена дважды.)

Важно понимать разницу между временем жизни указателя piи объекта, который он адресует. Сам объектpiявляется статическим. Следовательно, память под него выделяется в соответствие с правилами создания и уничтожения статических объектов. Совсем не так определяется время жизни адресуемого указателемpiобъекта, который создается с помощью оператораnewво время выполнения. Область памяти, на которую указываетpi, выделена динамически, следовательно,piявляется указателем на динамически размещенный объект типаint. Когда в программе встретится операторdelete, эта память будет освобождена. Однако память, отведенная самому указателюpi, не освобождается, а ее содержимое не изменяется. После выполненияdeleteобъектpiстановится висячим указателем, то есть ссылается на область памяти, не принадлежащую программе. Такой указатель служит источником трудно обнаруживаемых ошибок, поэтому сразу после уничтожения объекта ему полезно присвоить0 (NULL), обозначив таким образом, что указатель больше ни на что не ссылается.

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

Ниже приведены примеры опасных и безопасных операторов delete:

void f() {

int i;

char str[11] = "dwarves";

int *pi = &i;

short *ps = 0;

double *pd = new doub1e(33);

delete str; // плохо: str не является динамическим объектом

delete pi; // плохо: pi ссылается на локальный объект

delete ps; // безопасно

delete pd; // безопасно

}

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

  • не освободить выделенную память. В таком случае память не возвращается в хип. Эта ошибка получила название утечкипамяти;

  • дважды применить оператор deleteк одной и той же области памяти. Такое бывает, когда два указателя получают адрес одного и того же динамически размещенного объекта. В результате подобной ошибки мы вполне можем удалить нужный объект. Действительно, память, освобожденная с помощью одного из адресующих ее указателей, возвращается в хип и затем выделяется под другой объект. Затем операторdeleteприменяется ко второму указателю, адресовавшему старый объект, а удаляется при этом новый;

  • изменять объект после его удаления. Такое часто случается, поскольку указатель, к которому применяется оператор delete, не обнуляется.