Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ для начинающих (Стенли Липпман) 3-е хххх.pdf
Скачиваний:
86
Добавлен:
30.05.2015
Размер:
5.92 Mб
Скачать

С++ для начинающих

385

 

 

#include <iostream>

 

 

 

 

 

 

const int iterations = 2;

 

 

 

 

 

void func() {

 

 

 

 

 

 

 

int value1, value2; // не инициализированы

 

 

 

static int depth;

// неявно инициализирован нулем

 

 

 

if ( depth < iterations )

 

 

 

 

 

 

{ ++depth; func(); }

 

 

 

 

 

 

else depth = 0;

 

 

 

 

 

 

 

cout << "\nvaluel:\t" << value1;

 

 

 

 

 

cout << "\tvalue2:\t" << value2;

 

 

 

 

}

cout << "\tsum:\t" << value1 + value2;

 

 

 

 

 

 

 

 

 

 

 

int main() {

 

 

 

 

 

 

 

for ( int ix = 0; ix < iterations; ++ix ) func();

 

 

 

return 0;

 

 

 

 

 

 

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Вот результат работы программы:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

valuel:

0

value2:

74924

sum:

74924

 

 

 

valuel:

0

value2:

68748

sum:

68748

 

 

 

valuel:

0

value2:

68756

sum:

68756

 

 

 

valuel:

148620

value2:

2350

sum:

150970

 

 

 

valuel:

2147479844

value2:

671088640

sum:

-1476398812

 

 

 

valuel:

0

value2:

68756

sum:

68756

value1 и value2 неинициализированные автоматические объекты. Их начальные значения, как можно видеть из приведенной распечатки, оказываются случайными, и потому результаты сложения непредсказуемы. Объект depth, несмотря на отсутствие явной инициализации, гарантированно получает значение 0, и функция func() рекурсивно вызывает сама себя только дважды.

8.4. Динамически размещаемые объекты

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

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

С++ для начинающих

386

В этом разделе, посвященном динамическим объектам, мы рассмотрим три формы оператора new: для размещения единичного объекта, для размещения массива и третью форму, называемую оператором размещения new (placement new expression). Когда хип исчерпан, этот оператор возбуждает исключение. (Разговор об исключениях будет продолжен в главе 11. В главе 15 мы расскажем об операторах new и delete применительно к классам.)

8.4.1. Динамическое создание и уничтожение единичных объектов

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

new int;

размещает в хипе один объект типа int. Аналогично в результате выполнения

инструкции

new iStack;

там появится один объект класса iStack.

Сам по себе оператор new не слишком полезен. Как можно реально воспользоваться созданным объектом? Одним из аспектов работы с памятью из хипа является то, что размещаемые в ней объекты не имеют имени. Оператор 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.

С++ для начинающих

387

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

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

приблизительно эквивалентен следующей последовательности инструкций: int *pi = &ival; // указатель ссылается на этот объект

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

iStack *ps = new iStack( 512 );

создает объект типа iStack на 512 элементов. В случае объекта класса значение или значения в скобках передаются соответствующему конструктору, который вызывается в случае успешного выделения памяти. (Динамическое создание объектов классов более подробно рассматривается в разделе 15.8. Оставшаяся часть данного раздела посвящена созданию объектов встроенных типов.)

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

Время жизни объекта, на который памяти, где этот объект размещен. delete. Например,

указывает pi, заканчивается при освобождении Это происходит, когда pi передается оператору

delete pi;

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

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

// необходимо ли это? if ( pi != 0 )

какой-либо причине было нулевым? Не следует ли переписать этот код таким образом: delete pi;

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

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

С++ для начинающих

388

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

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

void f() { int i;

string str = "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 применяется ко второму указателю, адресовавшему старый объект, а удаляется при этом новый;

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

Эти ошибки при работе с динамически выделяемой памятью гораздо легче допустить, нежели обнаружить и исправить. Для того чтобы помочь программисту, стандартная библиотека С++ представляет класс auto_ptr. Мы рассмотрим его в следующем подразделе. После этого мы покажем, как динамически размещать и уничтожать массивы, используя вторую форму операторов new и delete.