Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Lections_rus.doc
Скачиваний:
31
Добавлен:
06.02.2016
Размер:
1.41 Mб
Скачать

6.4. Обработка исключений при динамическом выделении памяти

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

Достаточно распространенной особой ситуацией, требующей специальных действий на этапе выполнения программы, является невозможность выделить нужный участок памяти при ее динамическом распределении. Стандартное средство для такого запроса памяти - это операция new или перегруженные операции, вводимые с помощью операций-функций operator new ()иoperator new [] (). По умолчанию, если операция new не может выделить требуемое количество памяти, то она возвращает нулевое значение (NULL) и одновременно формирует исключение типаxalloc. Кроме того, в реализацию ВС++ включена специальная глобальная переменная_new_handler, значением которой служит указатель на функцию, которая запускается на выполнение при неудачном завершении операции-функцииoperator new (). По умолчанию функция, адресуемая указателем_new_handler, завершает выполнение программы.

Функция set_new_handler ()позволяет программисту назначить собственную функцию, которая будет автоматически вызываться при невозможности выполнить операциюnew.

Функция set_new_handler ()принимает в качестве параметра указательmy_handlerна ту функцию, которая должна автоматически вызываться при неудачном выделении памяти операциейnew.

Параметр my_handlerспецифицирован как имеющий типnew_handler, определенный в заголовочном файлеnew.hтаким образом:

typedef void (new *new_handler) () throw (xalloc);

В соответствии с приведенным форматом new_handler- это указатель на функцию без параметров, не возвращающую значения (void) и, возможно, порождающую исключение типаxalloc. Типxalloc- это класс, определенный в заголовочном файлеexcept.h.

Объект класса xalloc, созданный как исключение, передает информацию об ошибке при обработке запроса на выделение памяти. Классxallocсоздан на базе классаxmsg, который выдает сообщение, определяющее сформированное исключение. Определениеxmsgв заголовочном файлеexcept.hвыглядит так:

class xmsg

{

public:

xmsg(const string &msg);

xmsg(const xmsg &msg);

~xmsg();

const string & why() const;

void raise() throw (xmsg);

xmsg operator =(const xmsg &src);

private:

string _FAR *str;

};

Класс xmsgне имеет конструктора по умолчанию. Общедоступный (public) конструктор:xmsg (string msg)предполагает, что с каждымxmsg-объектом должно быть связано конкретное явно заданное сообщение типаstring. Типstringопределен в заголовочном файлеcstring.h.

Общедоступные (public) компонентные функции класса:

void raise () throw (xmsg);

вызов raise ()приводит к порождению исключенияxmsg. В частности, порождается *this.

inline const string _FAR &xmsg::why() const

{

return *str;

};

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

Класс xallocописан в заголовочном файлеexcept.hследующим образом:

class xalloc : public xmsg

{

public:

xalloc(const string &msg, size_t size); // Конструктор

size_t requested() const;

void raise() throw (xalloc);

private:

size_t siz;

};

Класс xallocне имеет конструктора по умолчанию, поэтому каждое определение объектаxallocдолжно включать сообщение, которое выдается в случае, если не может быть выделеноsizeбайт памяти. Типstringопределен в заголовочном файлеcstring.h.

Общедоступные (public) компонентные функции классаxalloc:

void xalloc::raise() throw (xalloc);

Вызов raise()приводит к порождению исключения типаxalloc. В частности, порождается *this.

inline size_t xalloc::requested() const

{

return siz;

}

Функция возвращает количество запрошенной для выделения памяти.

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

Определяемая программистом функция my_handler()должна выполнить одно из следующих действий:

  • вызвать библиотечную функцию abort()илиexit();

  • передать управление исходному обработчику этой ситуации;

  • освободить память и вернуть управление программе;

  • вызвать исключение типа xallocили порожденное от него.

Рассмотрим особенности перечисленных вариантов. Вызов функции abort()рассматривался ранее.

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

#include <iostream.h> // Описание потоков ввода/вывода

#include <new.h> // Описание функции set_new_handler()

#include <stdlib.h> // Описание функции abort()

// Прототип функции - старого обработчика ошибок памяти:

void (*old_new_handler)();

void new_new-handler() // Функция для обработки ошибок

{

cerr << "Ошибка при выделении памяти!";

of (old_new_handler) (*old_new_handler)();

abort(); // "Abnormal program termination"

}

int main (void)

{

// Устанавливаем собственный обработчик ошибок;

old_new_handler = set_new_handler(new_new_handler);

// Цикл с ограничением количества попыток выделения памяти:

for (int n = 1; n <= 1000; n++)

{

cout << n << «: »;

new char [61440U]; // Пытаемся выделить 60 Кбайт

cout << "Успех!" << endl;

}

return 0;

}

При установке собственного обработчика ошибок адрес старого (стандартного) обработчика сохраняется как значение указателя old_new_handler. Этот сохраненный адрес используется затем в функции для обработки ошибокnew_new_handler. С его помощью вместо библиотечной функцииabort()вызывается "старый" обработчик.

Результаты выполнения программы в среде Windows:

1: Успех! . . . 244: Успех! Ошибка при выделении памяти!

и затем сообщение в окне: "Program Aborted".

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

В следующем примере при нехватки памяти освобождаются блоки памяти, выделенной ранее, и управление возвращается программе. Для этого в программе определена глобальная переменная-указатель на блок (массив) символов (char *ptr).

#include <iostream.h> // Описание потоков ввода-вывода

#include <new.h> // Описание функции set_new_handler

char *ptr; // Указатель на блок (массив) символов

// Функция для обработки ошибок при выполнении операции new:

void new_new_handler()

{

cerr << "Ошибка при выделении памяти! ";

delete ptr; // Если выделить невозможно, удаляем последний блок

}

int main(void)

{

// Устанавливаем собственный обработчик ошибок:

set_new_handler (new_new_handler);

// Цикл с ограничением количества попыток выделения памяти:

for (int n = 1; n <= 1000; n++)

{

cout << n << ": ";

// Пытаемся выделить 60 Кбайт:

ptr = new char [61440U];

cout << "Успех! " << endl;

}

set_new_handler(0); // Отключаем все обработчики

return 0;

}

Результаты выполнения этой программы будет следующим (при запуске из командной строки DOS):

1: Успех!

. . .

6: Успех!

7: Ошибка при выделении памяти! Успех!

Если программа, выполняемая под Windows, не может получить требуемое количество памяти, то имеет смысл повторить попытки через некоторое время.

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

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

#include <except.h> // Описание класса xalloc

#include <iostream.h> // Описание потоков ввода/вывода

#include <cstring.h> // Описание класса string

int main (void)

{

try

{

for (int n = 1; n <= 1000; n++)

{

cout << n << ": ";

new char [61440U]; // Пытаемся выделить 60 Кбайт

cout << "Успех!" << endl;

}

}

catch (xalloc X)

{

cout << "При выделении памяти обнаружено ";

cout << "исключение "; << X.why();

}

return 0;

}

Результат выполнения программы (из командной строки DOS):

1: Успех!

. . .

6: Успех!

7: При выделении памяти обнаружено исключение Out of memory

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

cout << "Обнаружено исключение " << X.why();

cout << " при выделении ";

cout << X.request() << "байт памяти";

то результат и в этом случае будет не очень информативным:

7: Обнаружено исключение Out of memory при выделении 0 байт памяти.

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

#include <except.h> // Описание класса xalloc

#include <iostream.h> // Описание потоков ввода/вывода

#include <new.h> // Описание функции set_new_handler

#include <cstring.h> // Описание класса string

#define SIZE 61440U

// Функция для обработки ошибок при выполнении операции new:

void new_new_handler () throw (xalloc)

{

// Если память выделить не удалось,

// формируем исключение xalloc с соответствующими компонентами:

throw (xalloc (string ("Memory full"), SIZE));

}

int main (void)

{

// Устанавливаем собственный обработчик ошибок:

set_new_handler (new_new_handler);

try // Контролируемый блок

{

for (int n = 1; n <= 1000; n++)

{

cout << n << "; ";

new char [SIZE]; // Пытаемся выделить 60 Кбайт

cout << "Успех! " << endl;

}

}

catch (xalloc X) // Обработчик исключений

{

cout << "Обнаружено исключение " << X.why ();

cout << "привыделении";

cout << X.requested () << "байт памяти. ";

}

return 0;

}

Результат выполнения программы (из командной строки DOS):

1: Успех!

. . .

7: Обнаружено исключение Memory full при выделении 61440 байт памяти.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]