﻿# Поиск 64\-битных ошибок в реализации массивов

В PVS\-Studio 3\.43 был пересмотрен подход в обнаружении анализатором Viva64 ошибок в классах, представляющих собой контейнеры \(массивы\)\. Ранее мы придерживались позиции, что если в классе реализован operator\[\], то его параметр должен иметь memsize\-тип \(ptrdiff\_t, size\_t\), а не int или unsigned\. Мы и сейчас рекомендуем использовать для operator\[\] в качестве аргумента memsize тип\. Это позволяет компилятору построить в ряде случаев более эффективный код и заранее предотвращает некоторые 64\-битные ошибки\. Сейчас мы изменили подход к работе с классами, имеющими operator\[\], что позволяет сократить количество лишних диагностических предупреждений\.

Рассмотрим пример, который потенциально может содержать ошибку, если мы захотим работать с большими объемами данных:

```cpp
class MyArray {
  std::vector <float> m_arr;
  ...
  float &operator[](int i)
  {
    return m_arr[i];
  }
} A;
...
int x = 2000;
int y = 2000;
int z = 2000;
A[x * y * z] = 33;
```

Первый недостаток кода заключается в том, что _operator\[\] _не позволяет осуществить доступ к элементу с номером более _INT\_MAX_\.

Примечание\. Хочу уточнить один важный момент\. Для подобного кода, что показан в примере, компилятор в release\-версии может провести такую оптимизацию, что будет работать, так как будет использоваться 64\-битных регистр для вычисления и передачи индекса\. Я посвящу отдельный пост более подробному рассмотрению этого примера\. Однако это везение не делает код корректным\. Подробнее про опасные оптимизации смотрите [здесь](https://pvs-studio.ru/ru/blog/posts/cpp/a0043/)\.

Второй недостаток кода заключается в выражении _x\*y\*z_, в котором может возникнуть переполнение при работе с большим массивом\.

Ранее анализатор выдавал два предупреждения \([V108](https://pvs-studio.ru/ru/docs/warnings/v108/)\)\. Первое \- использование типа int при обращении к массиву _m\_arr_\. Второе \- использование типа int при обращении к массиву _A_\. Хотя _operator\[\]_ класса _MyArray_ принимает аргумент _int_, мы предлагали использовать в качестве индекса memsize\-тип\. Когда программист исправлял тип переменных _x_, _y_ и _z_ на _ptrdiff\_t_ компилятор Visual C\+\+ начинал предупреждать о приведении типа в строке _A\[x \* y \* z\] \= 33_:

warning C4244: 'argument' : conversion from 'ptrdiff\_t' to 'int', possible loss of data

Это предупреждение подсказывало пользователю изменить аргумент в _operator\[\]_ и код становился полностью корректным\. Пример исправленного кода:

```cpp
class MyArray {
  std::vector <float> m_arr;
  ...
  float &operator[](ptrdiff_t i)
  {
    return m_arr[i];
  }
} A;
...
ptrdiff_t x = 2000;
ptrdiff_t y = 2000;
ptrdiff_t z = 2000;
A[x * y * z] = 33;
```

К сожалению, у данного подхода диагностики выяснился существенный недостаток\. В ряде случаев _operator\[\]_ недоступен для изменения, или использование int в качестве индекса полностью оправдано\. При этом получалось, что анализатор Viva64 генерирует множество лишних предупреждений\. Примером может служить использование класса _CString_ из библиотеки MFC\. Оператор в классе _CString_ имеет прототип:

```cpp
TCHAR operator []( int nIndex ) const;
```

Из\-за этого данный код диагностировался как опасный:

```cpp
int i = x;
CString s = y;
TCHAR c = s[i];
```

Класс _CString_ недоступен для правки\. Да и вряд ли кто будет в стандартной программе использовать тип CString для работы со строками длиннее 2\-х миллиардов символов\. В свою очередь анализатор Viva64 выдавал множество предупреждений на данный код\. Если программист менял тип индекса с int на _ptrdiff\_t_, то предупреждения начинал выдавать компилятор\. Можно было использовать подавление предупреждений //\-V108, но это загромождает код\. Подробнее подавление предупреждений можно изучить в статье: [PVS\-Studio: использование функции "Mark as False Alarm"](https://pvs-studio.ru/ru/docs/manual/0017/)\.

Было принято решение считать конструкцию _A\[x \* y \* z\] \= 33;_ из первого примера безопасной\. Теперь если _operator\[\]_ в качестве аргумента принимает 32\-битный тип \(например, _int_\), и мы вызываем этот оператор так же используя 32\-битный тип, то данный вызов считается безопасным\.

Естественно это может замаскировать ошибку\. Поэтому было добавлено новое диагностическое сообщение [V302](https://pvs-studio.ru/ru/docs/warnings/v302/): "Member operator\[\] of 'FOO' class has a 32\-bit type argument\. Use memsize\-type here"\. Это диагностическое сообщение выводится для _operator\[\]_, объявленных с 32\-битным аргументом\.

Изящность этого решения заключается в том, что для библиотечного кода, к которому нет доступа для изменений, данное сообщение выводиться не будет\. То есть предупреждение V302 не будет выдано для класса _CString_, но будет выдано для пользовательского класса _MyArray_\.

Если _operator\[\] _в классе _MyArray_ корректен и действительно должен иметь тип _int_, то программисту будет достаточно вписать только одно подавление предупреждения //\-V302 в данном классе, а не во множестве мест, где он будет использоваться\.

Последнее изменение, связанное с обработкой массивов, касается введения еще одного предупреждения [V120](https://pvs-studio.ru/ru/docs/warnings/v120/): "Member operator\[\] of object 'FOO' declared with 32\-bit type argument, but called with memsize type argument"\. Это предупреждение в целом дублирует предупреждение компилятора о приведении 64\-битного типа к 32\-битному\. Оно будет полезно в том случае, когда предупреждений от компилятора много и в них теряется информация, связанная с работоспособностью кода на 64\-битной системе\.