// инициализируются нулем
double ad[100] = { }; // все элементы инициализируются нулем
char chars[] = { 'a', 'b', 'c' }; // нет завершающего нуля!
Обратите внимание на то, что количество элементов в массиве
ai
равно шести (а не семи), а количество элементов в массиве
chars
равно трем (а не четырем), — правило “добавить нуль в конце” относится только к строковым литералам. Если размер массива не задан явно, то он определяется по списку инициализации. Это довольно полезное правило. Если количество элементов в списке инициализации окажется меньше, чем количество элементов массива (как в определениях массивов
ai2
и
ad
), остальные элементы инициализируются значениями, предусмотренными для данного типа элементов по умолчанию.
18.5.4. Проблемы с указателями
Как и массивами, указателями часто злоупотребляют. Люди часто сами создают себе проблемы, используя указатели и массивы. В частности, все серьезные проблемы, связанные с указателями, вызваны обращением к области памяти, которая не является объектом ожидаемого типа, причем многие из этих проблем, в свою очередь, вызваны выходом за пределы массива. Перечислим эти проблемы.
• Обращение по нулевому указателю.
• Обращение по неинициализированному указателю.
• Выход за пределы массива.
• Обращение к удаленному объекту.
• Обращение к объекту, вышедшему из области видимости.
На практике во всех перечисленных ситуациях главная проблема, стоящая перед программистом, заключается в том, что внешне фактический доступ выглядит вполне невинно; просто указатель ссылается на неправильное значение. Что еще хуже (при записи с помощью указателя), проблема может проявиться намного позднее, когда окажется, что некий объект, не связанный с программой, был поврежден. Рассмотрим следующий пример.
Не обращайтесь к памяти с помощью нулевого указателя.
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). Компиляторы не способны распознать большинство проблем, связанных с возвращением указателей на локальные переменные, но некоторые из них они все же выявляют.