Литмир - Электронная Библиотека
A
A
Программирование. Принципы и практика использования C++ Исправленное издание - _191.png

Нам не известно, что собой представляют ячейки памяти, на которые ссылаются выражения

pd[–3]
и
pd[4]
. Однако мы знаем, что они не могут использоваться как часть нашего массива, в котором хранятся три числа типа
double
, на которые ссылается указатель
pd
. Вероятнее всего, они являются частью других объектов, и мы просто заблудились. Это плохо. Это катастрофически плохо. Здесь слово “катастрофически” означает либо “моя программа почему-то завершилась аварийно”, либо “моя программа выдает неправильные ответы”. Попытайтесь произнести это вслух; звучит ужасно. Нужно очень многое сделать, чтобы избежать подобных фраз. Выход за пределы допустимого диапазона представляет собой особенно ужасную ошибку, поскольку очевидно, что при этом опасности подвергаются данные, не имеющие отношения к нашей программе. Считывая содержимое ячейки памяти, находящегося за пределами допустимого диапазона, получаем случайное число, которое может быть результатом совершенно других вычислений. Записывая в ячейку памяти, находящуюся за пределами допустимого диапазона, можем перевести какой-то объект в “невозможное” состояние или просто получить совершенно неожиданное и неправильное значение. Такие действия, как правило, остаются незамеченными достаточно долго, поэтому их особенно трудно выявить. Что еще хуже: дважды выполняя программу, в которой происходит выход за пределы допустимого диапазона, с немного разными входными данными, мы можем прийти к совершенно разным результатам. Ошибки такого рода (неустойчивые ошибки) выявить труднее всего.

 

Программирование. Принципы и практика использования C++ Исправленное издание - _001.png
 Мы должны гарантировать, что выхода за пределы допустимого диапазона не будет. Одна из причин, по которым мы используем класс
vector
, а не выделяем память непосредственно с помощью оператора
new
, заключается в том, что класс
vector
знает свой размер и поэтому выход за пределы допустимого диапазона можно предотвратить.

Предотвратить выход за пределы допустимого диапазона сложно по многим причинам. Одна из них заключается в том, что мы можем присваивать один указатель

double*
другому указателю
double*
независимо от количества элементов, на которые они ссылаются. Указатель действительно не знает, на сколько элементов он ссылается. Рассмотрим пример.

double* p = new double;       // разместить переменную типа double

double* q = new double[1000]; // разместить тысячи переменных double

q[700] = 7.7;      // отлично

q = p;             // пусть указатель q ссылается на то же, что и p

double d = q[700]; // выход за пределы допустимого диапазона!

Здесь всего три строки кода, в которых выражение

q[700]
ссылается на две разные ячейки памяти, причем во втором случае происходит опасный выход за пределы допустимого диапазона.

Программирование. Принципы и практика использования C++ Исправленное издание - _192.png

Теперь мы надеемся, что вы спросите: “А почему указатель не может помнить размер памяти?” Очевидно, что можно было бы разработать указатель, который помнил бы, на какое количество элементов он ссылается, — в классе

vector
это сделано почти так. А если вы прочитаете книги, посвященные языку С++, и просмотрите его библиотеки, то обнаружите множество “интеллектуальных указателей”, компенсирующих этот недостаток встроенных низкоуровневых указателей. Однако в некоторых ситуациях нам нужен низкоуровневый доступ и понимание механизма адресации объектов, а машина не знает, что она адресует. Кроме того, знание механизма работы указателей важно для понимания огромного количества уже написанных программ.

17.4.4. Инициализация

Как всегда, мы хотели бы, чтобы объект уже имел какое-то значение, прежде чем мы приступим к его использованию; иначе говоря, мы хотели бы, чтобы указатели и объекты, на которые они ссылаются, были инициализированы. Рассмотрим пример.

double* p0;                   // объявление без инициализации:

                              // возможны проблемы

double* p1 = new double;      // выделение памяти для переменной

                              // типа double

                              // без инициализации

double* p2 = new double(5.5); // инициализируем переменную типа
double

                              // числом 5.5

double* p3 = new double[5];   // выделение памяти для массива

                              // из пяти чисел

                              // типа double без инициализации

Очевидно, что объявление указателя

p0
без инициализации может вызвать проблемы. Рассмотрим пример.

*p0 = 7.0;

 

Программирование. Принципы и практика использования C++ Исправленное издание - _003.png
 Эта инструкция записывает число
7.0
в некую ячейку памяти. Мы не знаем, в какой части памяти расположена эта ячейка. Это может быть безопасно, но рассчитывать на это нельзя. Рано или поздно мы получим тот же результат, что и при выходе за пределы допустимого диапазона: программа завершит работу аварийно или выдаст неправильные результаты. Огромное количество серьезных проблем в программах, написанных в старом стиле языка С, вызвано использованием неинициализированных указателей и выходом за пределы допустимого диапазона. Мы должны делать все, чтобы избежать таких проблем, частично потому, что наша цель — профессионализм, а частично потому, что мы не хотим терять время в поисках ошибок такого рода.

 

Программирование. Принципы и практика использования C++ Исправленное издание - _001.png
 Выявление и устранение таких ошибок — ужасно нудное и неприятное дело. Намного приятнее и продуктивнее предотвратить такие ошибки, чем вылавливать их.

 

Программирование. Принципы и практика использования C++ Исправленное издание - _003.png
 Память, выделенная оператором new встроенных типов, не инициализируется. Если хотите инициализировать указатель, задайте конкретное значение, как это было сделано при объявлении указателя
p2: *p2
равно
5.5
. Обратите внимание на круглые скобки,
()
, используемые при инициализации. Не перепутайте их с квадратными скобками,
[]
, которые используются для индикации массивов.

В языке С++ нет средства для инициализации массивов объектов встроенных типов, память для которых выделена оператором

new
. Для массивов работу придется проделать самостоятельно. Рассмотрим пример.

225
{"b":"847443","o":1}