﻿# Как обнаружить переполнение 32\-битных переменных в длинных циклах в 64\-битной программе?

Одна из проблем, с которой сталкиваются разработчики 64\-битных приложений, это переполнение 32\-битных переменных в очень длинных циклах\. С этой задачей хорошо справляется анализатор кода PVS\-Studio \(набор диагностик Viva64\)\. На тему переполнения переменных в циклах есть ряд вопросов на сайте stackoverflow\.com\. Но поскольку мои ответы могут счесть исключительно рекламными, а не как полезную информацию, я решил описать возможности PVS\-Studio в статье\.

Типовой конструкцией языка C/C\+\+ является цикл\. При портирования программ на 64\-битную архитектуру, циклы неожиданно становятся слабым местом, так как при разработке кода редко кто заранее задумывался, что произойдет, если программе придётся выполнять миллиарды итераций\.

В своих статьях мы называем такие ситуации 64\-битными ошибками\. На самом деле это просто ошибки\. Но их особенность в том, что проявляют они себя только в 64\-битной программе\. В 32\-битной программе просто не возникают столь длинные циклы, да и невозможно создать массив, с количеством элементов больше _INT\_MAX_\.

Итак, проблема\. В 64\-битной программе происходит переполнение целочисленных 32\-битных типов\. Речь идёт о таких типах как _int_, _unsigned_, _long_ \(если это [Win64](https://pvs-studio.ru/ru/blog/terms/0026/)\)\. Необходимо как\-то выявить все такие опасные места\. Это может сделать анализатор PVS\-Studio, о чем мы и поговорим\.

Рассмотрим различные варианта переполнения переменных, связанных с длинными циклами\.

Первая ситуация\. Описана на сайте Stack Overflow здесь: "[How can elusive 64\-bit portability issues be detected?](https://stackoverflow.com/questions/6336403/how-can-elusive-64-bit-portability-issues-be-detected)"\. Имеется код следующего вида:

```cpp
int n;
size_t pos, npos;
/* ... initialization ... */
while((pos = find(ch, start)) != npos)
{
    /* ... advance start position ... */
    n++; // this will overflow if the loop iterates too many times
}
```

Программа обрабатывает очень длинные строки\. В 32\-битной программе длина строка не сможет превысить _INT\_MAX_\. Поэтому никакой ошибки произойти не может\. Да, программа не может обработать какие\-то большие объемы данных, но это не ошибка, а ограничение возможностей 32\-битной архитектуры\.

В 64\-битной программе длина строки уже может быть больше _INT\_MAX_ и соответственно переменная _n_ может переполниться\. Это приведёт к неопределённому поведению программы\. Не надо думать, что переполнение просто превратит число 2147483647 в \-2147483648\. Это именно неопределённое поведение и предсказать последствия невозможно\. Для тех, кто не верит, что переполнение знаковой переменной приводит к неожиданным изменениям в работе программы, предлагаю познакомиться с моей статьёй "[Undefined behavior ближе, чем вы думаете](https://pvs-studio.ru/ru/blog/posts/cpp/0374/)"\.

Итак, нужно обнаружить, что переменная _n_ может переполниться\. Нет ничего проще\. Запускаем PVS\-Studio и получаем предупреждение: 

[V127](https://pvs-studio.ru/ru/docs/warnings/v127/) An overflow of the 32\-bit 'n' variable is possible inside a long cycle which utilizes a memsize\-type loop counter\. mfcapplication2dlg\.cpp 190

Если изменить тип переменной _n_ на _size\_t_, то ошибка, а соответственно и сообщение анализатора, исчезнет\.

Там же приводится ещё один пример кода, который требуется выявить:

```cpp
int i = 0;
for (iter = c.begin(); iter != c.end(); iter++, i++)
{
    /* ... */
}
```

Запускаем PVS\-Studio и вновь получаем предупреждение V127:

V127 An overflow of the 32\-bit 'i' variable is possible inside a long cycle which utilizes a memsize\-type loop counter\. mfcapplication2dlg\.cpp 201

В теме на Stack Overflow также поднимается вопрос, что делать если кодовая база огромна и как найти все подобные ошибки\.

Как видим, эти ошибки можно обнаруживать с помощью статического анализатора кода PVS\-Studio\. И это единственный способ совладать с большим проектом\. Так же надо отметить, что PVS\-Studio предоставляет удобный интерфейс для работы с большим количеством диагностических сообщений\. Вы можете интерактивно фильтровать сообщения, помечать их как ложные и так далее\. Однако описание возможностей PVS\-Studio выходит за рамки этой заметки\. Для тех, кто заинтересовался инструментом предлагаю познакомиться со следующим материалами:

* Статья [PVS\-Studio для Visual C\+\+](https://pvs-studio.ru/ru/blog/posts/cpp/0305/)\.
* Статья [Практика использования анализатора PVS\-Studio](https://pvs-studio.ru/ru/blog/posts/csharp/0364/)\.
* [Документация](https://pvs-studio.ru/ru/docs/)\.

Отмечу также, что мы имеем [опыт портирования](https://pvs-studio.ru/ru/blog/posts/0342/) большого проекта в 9 млн\. строк кода на 64\-битную платформу\. И PVS\-Studio отлично показал себя в работе над этим проектом\.



Перейдем к следующей теме на сайте Stack Overflow: "[Can Klocwork \(or other tools\) be aware of types, typedefs and \#define directives?](https://stackoverflow.com/questions/6443223/can-klocwork-or-other-tools-be-aware-of-types-typedefs-and-define-directives)"\.

Как я понимаю, человек поставил для себя задачу найти подходящий инструмент для поиска всех циклов, организованных с помощью 32\-битных счетчиков цикла\. Т\.е\. другими словами где используется тип _int_\.

Это задача несколько отличается от предыдущей\. Но такие циклы действительно следует искать\. Ведь с помощью переменной _int_ невозможно обрабатывать огромные массивы и так далее\.

Подошел человек к решению задачи неправильно\. В этом он не виноват\. Он просто не знает о существовании PVS\-Studio\. Сейчас вы поймете почему я так говорю\.

Итак, он планирует искать:

```cpp
for (int i = 0; i < 10; i++)
    // ...
```

Это ужасно\. Придётся просмотреть невероятное количество циклов, с целью понять, могут они привести к ошибке или нет\. Это огромная работа и вряд ли её можно делать, не теряя внимание\. Скорее всего будут пропущены многие опасные места\.

Править все циклы подряд, заменяя _int_, например, на _intptr\_t_ тоже плохой вариант\. Это очень много работы и изменений в коде\.

Анализатор PVS\-Studio может помочь\. Приведённый выше цикл он не найдёт\. Потому, что его и не надо искать\. В нём просто нет места для ошибки\. Цикл выполняет 10 итераций\. И никакого переполнения в нем быть не может\. Так что нечего программисту тратить время на этот участок кода\.

Зато анализатор укажет вот на такие циклы:

```cpp
void Foo(std::vector<float> &v)
{
  for (int i = 0; i < v.size(); i++)
    v[i] = 1.0;
}
```

Анализатор выдаст сразу 2 предупреждения\. Первое предупреждает о том, что в выражении 32\-битный тип сравнивается с [memsize\-типом](https://pvs-studio.ru/ru/blog/terms/0030/):

[V104](https://pvs-studio.ru/ru/docs/warnings/v104/) Implicit conversion of 'i' to memsize type in an arithmetic expression: i < v\.size\(\) mfcapplication2dlg\.cpp 210

И действительно, тип переменной _i_ не подходит для организации длинных циклов\.

Второе предупреждение говорит, что странно в качестве индекса использовать 32\-битную переменную\. Если массив большой, то код ошибочен\.

[V108](https://pvs-studio.ru/ru/docs/warnings/v108/) Incorrect index type: v\[not a memsize\-type\]\. Use memsize type instead\. mfcapplication2dlg\.cpp 211

Корректный код должен выглядеть так:

```cpp
void Foo(std::vector<float> &v)
{
  for (std::vector<float>::size_type i = 0; i < v.size(); i++)
    v[i] = 1.0;
}
```

Код стал длинным и некрасивым, поэтому появляется соблазн использовать ключевое слово _auto_, но этого делать нельзя \- измененный таким образом код вновь некорректен:

```cpp
for (auto i = 0; i < v.size(); i++)
  v[i] = 1.0;
```

Так как константа 0 имеет тип _int_, то и переменная _i_ будет иметь тип _int_\. И мы вернулись к тому, с чего начали\. Кстати раз зашла речь о новых возможностях стандарта языка С\+\+, предлагаю взглянуть на статью "[C\+\+11 и 64\-битные ошибки](https://pvs-studio.ru/ru/blog/posts/cpp/0253/)"\.

Думаю, можно пойти на компромисс и написать не идеальный, но правильный код:

```cpp
for (size_t i = 0; i < v.size(); i++)
  v[i] = 1.0;
```

_**Примечание\.** Конечно, ещё более правильным будет использовать итераторы или алгоритм fill\(\)\. Но мы говорим о поисках переполнения 32\-битных переменных в старых программах\. Поэтому я и не рассматриваю такие варианты исправления кода\. Это уже совсем другая тема\._

Хочу подчеркнуть, что анализатор достаточно умён и старается почем зря не беспокоить программиста\. Например, он не будет выдавать предупреждения, если увидит, что обрабатывается маленький массив:

```cpp
void Foo(int n)
{
  float A[100];
  for (int i = 0; i < n; i++)
    A[i] = 1.0;
}
```

**Заключение**

Анализатор PVS\-Studio является лидером по поиску 64\-битных ошибок\. Изначально, он как раз и создавался для помощи программистам в портирования их программ на 64\-битные системы\. В то время он ещё назывался Viva64\. Это уже потом, он превратился в анализатор общего назначения, но существовавшие 64\-битные диагностики никуда не исчезли и всё также готовы вам помочь\.

Скачать демонстрационную версию можно [здесь](https://pvs-studio.ru/ru/pvs-studio/download/)\.

Подробнее [о разработке 64\-битных программ](https://pvs-studio.ru/ru/blog/lessons/full/)\.