В PVS-Studio 3.43 был пересмотрен подход в обнаружении анализатором Viva64 ошибок в классах, представляющих собой контейнеры (массивы). Ранее мы придерживались позиции, что если в классе реализован operator[], то его параметр должен иметь memsize-тип (ptrdiff_t, size_t), а не int или unsigned. Мы и сейчас рекомендуем использовать для operator[] в качестве аргумента memsize тип. Это позволяет компилятору построить в ряде случаев более эффективный код и заранее предотвращает некоторые 64-битные ошибки. Сейчас мы изменили подход к работе с классами, имеющими operator[], что позволяет сократить количество лишних диагностических предупреждений.
Рассмотрим пример, который потенциально может содержать ошибку, если мы захотим работать с большими объемами данных:
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-битных регистр для вычисления и передачи индекса. Я посвящу отдельный пост более подробному рассмотрению этого примера. Однако это везение не делает код корректным. Подробнее про опасные оптимизации смотрите здесь.
Второй недостаток кода заключается в выражении x*y*z, в котором может возникнуть переполнение при работе с большим массивом.
Ранее анализатор выдавал два предупреждения (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[] и код становился полностью корректным. Пример исправленного кода:
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 имеет прототип:
TCHAR operator []( int nIndex ) const;
Из-за этого данный код диагностировался как опасный:
int i = x;
CString s = y;
TCHAR c = s[i];
Класс CString недоступен для правки. Да и вряд ли кто будет в стандартной программе использовать тип CString для работы со строками длиннее 2-х миллиардов символов. В свою очередь анализатор Viva64 выдавал множество предупреждений на данный код. Если программист менял тип индекса с int на ptrdiff_t, то предупреждения начинал выдавать компилятор. Можно было использовать подавление предупреждений //-V108, но это загромождает код. Подробнее подавление предупреждений можно изучить в статье: PVS-Studio: использование функции "Mark as False Alarm".
Было принято решение считать конструкцию A[x * y * z] = 33; из первого примера безопасной. Теперь если operator[] в качестве аргумента принимает 32-битный тип (например, int), и мы вызываем этот оператор так же используя 32-битный тип, то данный вызов считается безопасным.
Естественно это может замаскировать ошибку. Поэтому было добавлено новое диагностическое сообщение 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: "Member operator[] of object 'FOO' declared with 32-bit type argument, but called with memsize type argument". Это предупреждение в целом дублирует предупреждение компилятора о приведении 64-битного типа к 32-битному. Оно будет полезно в том случае, когда предупреждений от компилятора много и в них теряется информация, связанная с работоспособностью кода на 64-битной системе.