Подобные ошибки означают, что во время работы программы была предпринята попытка обращения к памяти, не подготовленной должным образом для этого. Программа с такими ошибками содержит неопределённое поведение. Самыми говорящими из рассмотренных в вопросе ошибок являются те, где сказано "subscript out of range". Дословно это переводится как "Индексация вне диапазона". Понятность их обеспечивается в первую очередь тем, что программа собрана в отладочном (Debug) режиме и соответствующий код индексации operator[] того или иного контейнера (std::array, std::string, std::vector) непосредственно содержит проверку значения индекса, передаваемого в оператор индексации. Например, так выглядит код в msvc 2019 для std::array:
_NODISCARD _CONSTEXPR17 reference operator[](_In_range_(0, _Size - 1) size_type _Pos) noexcept /* strengthened */ {
#if _CONTAINER_DEBUG_LEVEL > 0
_STL_VERIFY(_Pos < _Size, "array subscript out of range");
#endif // _CONTAINER_DEBUG_LEVEL > 0
return _Elems[_Pos];
}
Другие сообщения не так очевидны, потому что возникают при работе приложений, собранных без отладочной информации и/или оптимизированных так, что подробное отладочное сообщение не содержится/не связано с выполняемый кодом, но причины их возникновения часто те же самые.
Решение проблемы прямо вытекает из текста отладочных сообщений - индекс, по которому идёт обращение к элементу контейнера, должен быть в допустимом диапазоне. Т.е. если контейнер имеет размер N, допустимыми индексами будут являться 0...N-1. Исключением из этого правила является std::string, где допускается так же использовать индекс N, но с оговоркой, что писать туда можно только нулевой символ \0, и он же вернётся при чтении.
Довольно часто проблема выхода за допустимые границы диапазона случается в циклах, когда условие завершение содержит нестрогое сравнение индекса с размером контейнера: т.е. i <= size вместо i < size. В подобных случаях итерации по элементам, следует по возможности пользоваться диапазонной версией цикла for, которая не допускает выхода за пределы контейнера, если размер контейнера остаётся постоянным в процессе итерирования.
std::vector<int> v = {1, 2, 3};
for(int i = 0; i <= v.size(); ++i) // ошибка. <= вместо <
v[i] = 42; // проблема на последней итерации
for(auto& e : v) // диапазонный for
e = 42; // e всегда принадлежит вектору
Иногда цикл кажется правильным, но вместо v.size() по незнанию указывают v.max_size(), который говорит вообще о потенциально возможном размере контейнера для данной архитектуры, а не о текущем размере. Понятно, что в этом случае можно выйти далеко за пределы разрешённых величин.
При этом даже в циклах, где правильно заданы границы индексов можно использовать не тот элемент, т.к. конкретный индекс вычисляется как функция от индекса из цикла, например:
for(int i = 0; i < v.size(); ++i) {
v[f(i)] = ... // f(i) может возвращать другой диапазон
v[i - 1] = ... // использование "предыдущего" индекса. Ошибка для i == 0.
}
Другой возможный случай - когда возникает путаница между ассоциативными контейнерами (где operator[] приводит к созданию/добавлению элемента в контейнер, если его ещё не было) и последовательными контейнерами. Например:
std::map<int, int> m;
m[0] = 42; // ok, добавляется элемент с ключом 0 и ему присваивается значение 42
std::vector<int> v;
v[0] = 42; // ошибка, вектор пустой, элемента с индексом 0 не существует
Добавить элемент в вектор можно либо при инициализации std::vector<int> v = {42};, либо позднее, например, через v.push_back(42).
Иногда ошибка может возникать, когда вместо std::vector::resize был вызван std::vector::reserve. Т.е. память выделена и доступ идёт только к элементам внутри выделенной памяти, но логически размер вектора не был изменён (элементы вектора фактически не проинициализированы). При Release сборке в этом случае может вовсе не возникнуть ошибки доступа (access violation), но такая программа не будет считаться валидной.
Чтобы обеспечить дополнительную проверку диапазона используемых индексов как в отладочном, так и в релизном режимах сборки вместо operator[] можно воспользоваться функцией at(), которая выбросит исключение std::out_of_range при использовании индекса вне разрешённого диапазона. В некоторых случаях сложного вычисления индекса это может быть оправдано, но внесёт дополнительный оверхед.
std::string, где допускается так же использовать индексN, но только на чтение». Ну, писать туда вроде как тоже можно, но только нулевой символ. См.: cppreference.com и string.access / 2. – wololo Nov 21 '21 at 16:19