Выход за границу массива (Array index out of bounds) — это частный случай переполнения буфера. Ошибка возникает, если индекс, с помощью которого обращаются к элементам массива, превышает допустимое значение. При этом идёт обращение за границы массива, что является неопределенным поведением. Это возможно из-за того, что в языках программирования С и С++ нет строгого контроля выхода за границы массива: наличие проверок зависит от используемых инструментов и их реализации.
Диагностировать выход за границы массива можно с помощью инструментов статического или динамического анализа кода. Эти диагностики очень актуальны, поскольку такие ошибки могут проявляться не сразу. Работоспособность программы будет зависеть от версии компилятора, операционной системы, затронутых данных и т.д.
Рассмотрим данную ошибку на примерах, найденных в коде реальных Open Source проектов с помощью статического анализатора PVS-Studio.
Проект Dumb (динамическая универсальная музыкальная библиотека):
struct IT_SAMPLE
{
....
unsigned char filename[14];
....
};
static int it_riff_dsmf_process_sample(
IT_SAMPLE * sample, const unsigned char * data, int len)
{
int flags;
memcpy( sample->filename, data, 13 );
sample->filename[ 14 ] = 0;
....
}
Массив filename состоит из 14 элементов, однако в функции it_riff_dsmf_process_sample идёт обращение к 14-му элементу, который находится за границами массива. Такую ошибку часто допускают, забывая, что индексация массивов в языках С и С++ начинается с нуля и заканчивается значением на единицу меньше размера массива (в данном случае — 13).
Рассмотрим ещё одну подобную ошибку. Проект Wolfenstein 3D (компьютерная игра, разработанная компанией id Software):
typedef struct bot_state_s
{
...
char teamleader[32]; //netname of the team leader
...
} bot_state_t;
void BotMatch_StartTeamLeaderShip(
bot_state_t *bs, bot_match_t *match)
{
...
bs->teamleader[sizeof( bs->teamleader )] = '\0'; //< =
...
}
В данном случае ошибка заключается в том, что sizeof(Array) вернёт размер массива, а для обращения к последнему элементу следует вычесть единицу из результата sizeof(Array).
Также здесь стоит сделать ремарку, что выбранный разработчиками способ доступа к последнему элементу не является надежным, т.к. работает только если массив содержит элементы размером 1 байт (char, unsigned char и т.п.). Более правильный способ получения индекса последнего элемента выглядит так:
const size_t lastPos = sizeof(bs->teamleader) /
sizeof(bs->teamleader[0]) - 1;
bs->teamleader[lastPos] = '\0';
Здесь мы получаем общий размер массива в байтах и делим его на размер первого элемента, тем самым получая количество элементов в массиве. В конце остаётся только вычесть единицу. Полученное значение и будет индексом последнего элемента в массиве.
На самом деле, даже указанное выше исправление не является хорошим. Если вы пишите именно на С++, то для вас существуют более безопасные и удобные способы работы с массивами. Подробнее мы рассмотрели их в статье "Как не надо проверять размер массива в С++", а именно в разделе "Современный C++: правильное вычисление количества элементов в массивах и контейнерах".
Другие примеры таких ошибок, найденных в Open Source проектах с помощью статического анализатора PVS-Studio, можно посмотреть здесь.
0