Вход/Регистрация
Программирование. Принципы и практика использования C++ Исправленное издание
вернуться

Страуструп Бьерн

Шрифт:

• Выход за пределы массива.

• Обращение к удаленному объекту.

• Обращение к объекту, вышедшему из области видимости.

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

Не обращайтесь к памяти с помощью нулевого указателя.

int* p = 0;

*p = 7; // Ой!

Очевидно, что в реальной программе это может произойти, если между инициализацией и использованием указателя размещен какой-то код. Чаще всего эта ошибка возникает при передаче указателя p функции или при получении его в результате работы функции. Мы рекомендуем никуда не передавать нулевой указатель, но, уж если вы это сделали, проверьте указатель перед его использованием. Например,

int* p = fct_that_can_return_a_0;

if (p == 0) {

// что-то делаем

}

else {

// используем р

*p = 7;

}

и

void fct_that_can_receive_a_0(int* p)

{

if (p == 0) {

// что-то делаем

}

else {

// используем р

*p = 7;

}

}

Основными средствами, позволяющими избежать ошибок, связанных с нулевыми указателями, являются ссылки (см. раздел 17.9.1) и исключения (см. разделы 5.6 и 19.5).

Инициализируйте указатели.

int* p;

*p = 9; // Ой!

В частности, не забывайте инициализировать указатели, являющиеся членами класса.

Не обращайтесь к несуществующим элементам массива.

int a[10];

int* p = &a[10];

*p = 11; // Ой!

a[10] = 12; // Ой!

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

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

Не обращайтесь к памяти с помощью удаленного указателя.

int* p = new int(7);

// ...

delete p;

// ...

*p = 13; // Ой!

Инструкция

delete p
или код, размещенный после нее, может неосторожно обратиться к значению
*p
или использовать его косвенно. Все эти ситуации совершенно недопустимы. Наиболее эффективной защитой против этого является запрет на использование “голых” операторов
new
, требующих выполнения “голых” операторов
delete
: выполняйте операторы
new
и
delete
в конструкторах и деструкторах или используйте контейнеры, такие как
Vector_ref
(раздел Д.4).

Не возвращайте указатель на локальную переменную.

int* f

{

int x = 7;

// .. .

return &x;

}

// ...

int* p = f;

// ...

*p = 15; // Ой!

Возврат из функции

f
или код, размещенный после него, может неосторожно обратиться к значению
*p
или использовать его косвенно. Причина заключается в том, что локальные переменные, объявленные в функции, размещаются в стеке перед вызовом функции и удаляются из него при выходе. В частности, если локальной переменной является объект класса, то вызывается его деструктор (см. раздел 17.5.1). Компиляторы не способны распознать большинство проблем, связанных с возвращением указателей на локальные переменные, но некоторые из них они все же выявляют.

Рассмотрим эквивалентный пример.

vector& ff

{

vector x(7);

// ...

return x;

} // здесь вектор х был уничтожен

// ...

vector& p = ff;

// ...

p[4] = 15; // Ой!

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

new
и
delete
. Если же вы поступаете так, то просто быть осторожным в реальной жизни недостаточно. Полагайтесь на векторы, концепцию RAII (“Resource Acquisition Is Initialization” — “Получение ресурса — это инициализация”; см. раздел 19.5), а также на другие систематические подходы к управлению памятью и другими ресурсами.

  • Читать дальше
  • 1
  • ...
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • ...

Ебукер (ebooker) – онлайн-библиотека на русском языке. Книги доступны онлайн, без утомительной регистрации. Огромный выбор и удобный дизайн, позволяющий читать без проблем. Добавляйте сайт в закладки! Все произведения загружаются пользователями: если считаете, что ваши авторские права нарушены – используйте форму обратной связи.

Полезные ссылки

  • Моя полка

Контакты

  • chitat.ebooker@gmail.com

Подпишитесь на рассылку: