# V108. Incorrect index type: 'foo[not a memsize-type]'. Use memsize type instead.

Анализатор обнаружил потенциально возможную ошибку индексации больших массивов. Ошибка может заключаться в некорректном вычислении индекса.

Первый пример.

    extern char *longString;
    extern bool *isAlnum;
    ...
    unsigned i = 0;
    while (*longString) {
      isAlnum[i] = isalnum(*longString++);
      ++i;
    }

Данный код совершенно корректен для 32-битной платформы, где принципиально невозможна обработка массивов более 'UINT_MAX' байт (4 Гб). На 64-битной платформе можно обработать массив размером более 4 Гб, что иногда очень удобно. Но корректно обработать большой массив приведенным алгоритмом невозможно. Ошибка заключается в использовании для индексации массива 'isAlnum' переменной типа 'unsigned'. Когда мы заполним первые 'UINT_MAX' элементов, произойдет переполнение переменной 'i', и она приравняется нулю. В результате мы начнем перезаписывать элементы массива 'isAlnum' расположенные в начале, а часть элементов оставим неинициализированными.

Корректным исправлением является изменения типа переменной 'i' на memsize тип:

    ...
    size_t i = 0;
    while (*longString)
      isAlnum[i++] = isalnum(*longString++);

Второй пример.

    class Region {
      float *array;
      int Width, Height, Depth;
      float Region::GetCell(int x, int y, int z) const;
      ...
    };
    float Region::GetCell(int x, int y, int z) const {
      return array[x + y * Width + z * Width * Height];
    }

Для программ численного моделирования важным ресурсом является объем оперативной памяти, и возможность на 64-битной архитектуре использовать более 4 Гб памяти существенно увеличивает вычислительные возможности. В таких программах часто используют одномерные массивы, работая затем с ними как с трехмерными. Для этого существуют функции, аналогичные 'GetCell', обеспечивающие доступ к необходимым элементам счетной области. Но приведенный код может корректно работать с массивами, содержащими не более 'INT_MAX' (2 Гб) элементов. Причина заключается в использовании 32-битных типов 'int', участвующих в вычислении индекса элемента. Если количество элементов в массиве 'array' превысит 'INT_MAX' (2 Гб), то произойдет переполнение и значение индекса будет вычислено некорректно. Программисты часто допускают ошибку, пытаясь исправить код следующим образом:

    float Region::GetCell(int x, int y, int z) const {
      return array[static_cast<ptrdiff_t>(x) + y * Width +
                        z * Width * Height];
    }

Они знают, что по правилам языка Си++ выражение для вычисления индекса будет иметь тип 'ptrdiff_t' и надеются за счет этого избежать переполнения. К сожалению, переполнение может произойти внутри подвыражения "y * Width или z * Width * Height" ,так как для их вычисления по-прежнему используется тип 'int'.

Если вы хотите исправить код, не изменяя типов переменных, участвующих в выражении, то вы должны явно привести каждую переменную к memsize типу:

    float Region::GetCell(int x, int y, int z) const {
      return array[ptrdiff_t(x) +
     ptrdiff_t(y) * ptrdiff_t(Width) +
     ptrdiff_t(z) * ptrdiff_t(Width) *
     ptrdiff_t(Height)];
    }

Другое решение - изменить типы переменных на memsize тип:

    class Region {
      float *array;
      ptrdiff_t Width, Height, Depth;
      float
        Region::GetCell(ptrdiff_t x, ptrdiff_t y, ptrdiff_t z) const;
      ...
    };
    float Region::GetCell(ptrdiff_t x, ptrdiff_t y, ptrdiff_t z) const
    {
      return array[x + y * Width + z * Width * Height];
    }

Если вы используете для индексации выражения, имеющие тип отличный от memsize, но при этом точно уверены в корректности кода, то для подавления диагностирующих сообщений анализатора можно использовать явное приведение типов, как показано ниже.

    bool *Seconds;
    int min, sec;
    ...
    bool flag = Seconds[static_cast<size_t>(min * 60 + sec)];

Если вы подозреваете в программе наличие ошибок, связанных с некорректным явным приведением типов в выражениях, то вы можете воспользоваться предупреждением V201.

По возможности анализатор пытается понять, когда использование не memsize-типа в качестве индекса массива безопасно и не выдавать в этом месте предупреждения. В результате поведение анализатора иногда может показаться странным. В таких ситуациях мы просим не спешить и постараться разобраться, в ситуации. Рассмотрим следующий код:

    char Arr[] = { '0', '1', '2', '3', '4' };
    char *p = Arr + 2;
    cout << p[0u + 1] << endl;
    cout << p[0u - 1] << endl; //V108

Данный код исправно работает в 32-битном режиме и печатает на экране числа 3 и 1. При проверке этого кода мы получим предупреждение только на одну строку с выражением "p[0u - 1]". И это совершенно верно. Если вы скомпилируете и запустите данный пример в 64-битном режиме, то увидите, как на экране будет распечатано значение 3, после чего произойдет аварийное завершений программы.

Ошибка связана с тем, что индексация "p[0u - 1]" на 64-битной системе некорректна, о чем и предупреждает анализатор. Согласно правилам языка Си++ выражение "0u - 1" будет иметь тип unsigned и равняться 0xFFFFFFFFu. На 32-битной архитектуре сложение указателя с этим числом будет эквивалентно вычитанию единицы. А на 64-битной системе к указателю будет честно прибавлено 0xFFFFFFFFu и произойдет обращение к памяти за приделами массива.

Конечно, часто индексация к массивам с использованием таких типов как int и unsigned безопасна. В этом случае предупреждения анализатора могут казаться не уместными. Но следует учитывать, что такой код все равно может оказаться ненадежным в случае его модернизации для обработки другого набора данных. Код с использованием типов int и unsigned может оказаться менее производительным, чем это возможно на 64-битной архитектуре.

Если вы уверены в корректности индексации, то вы можете воспользоваться функцией "Suppression of false alarms" или использовать фильтры. Можно использовать явное приведение типов в коде:

    for (int i = 0; i != n; ++i)
      Array[static_cast<ptrdiff_t>(i)] = 0;

Дополнительные материалы по данной теме:

- 64-битные уроки. Урок 13. Паттерн 5. Адресная арифметика.