Выбрать главу

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

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

Упражнения раздела 3.5.2

Упражнение 3.30. Выявите ошибки индексации в следующем коде

constexpr size_t array size = 10;

int ia[array_size];

for (size_t ix = 1; ix <= array size; ++ix)

 ia[ix] = ix;

Упражнение 3.31. Напишите программу, где определен массив из десяти целых чисел, каждому элементу которого присвоено значение, соответствующее его позиции в массиве.

Упражнение 3.32. Скопируйте массив, определенный в предыдущем упражнении, в другой массив. Перезапишите эту программу так, чтобы использовались векторы.

Упражнение 3.33. Что будет, если не инициализировать массив scores в программе оценок из данного раздела?

3.5.3. Указатели и массивы

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

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

string nums[] = {"one", "two", "three"}; // массив строк

string *p = &nums[0]; // p указывает на первый элемент массива nums

Однако у массивов есть одна особенность — места их использования компилятор автоматически заменяет указателем на первый элемент.

string *p2 = nums; // эквивалент p2 = &nums[0]

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

Существует множество свидетельств того факта, что операции с массивами зачастую являются операциями с указателями. Одно из них — при использовании массива как инициализатора переменной, определенной с использованием спецификатора auto (см. раздел 2.5.2), выводится тип указателя, а не массива.

int ia[] = {0,1,2,3,4,5,6,7,8,9}; // ia - массив из десяти целых чисел

auto ia2(ia); // ia2 - это int*, указывающий на первый элемент в ia

ia2 = 42;     // ошибка: ia2 - указатель, нельзя присвоить указателю

              // значение типа int

Хотя ia является массивом из десяти целых чисел, при его использовании в качестве инициализатора компилятор рассматривает это как следующий код:

auto ia2(&ia[0]); // теперь ясно, что ia2 имеет тип int*

Следует заметить, что это преобразование не происходит, если используется спецификатор decltype (см. раздел 2.5.3). Выражение decltype(ia) возвращает массив из десяти целых чисел:

// ia3 - массив из десяти целых чисел

decltype(ia) ia3 = {0,1,2,3,4,5,6,7,8,9};

ia3 = p;    // ошибка: невозможно присвоить int* массиву

ia3[4] = i; // ok: присвоить значение i элементу в массиве ia3

Указатели — это итераторы

Указатели, содержащие адреса элементов в массиве, обладают дополнительными возможностями, кроме описанных в разделе 2.3.2. В частности, указатели на элементы массивов поддерживают те же операции, что и итераторы векторов или строк (см. раздел 3.4). Например, можно использовать оператор инкремента для перемещения с одного элемента массива на следующий: