﻿# Оптимизация в мире 64\-битных ошибок

В предыдущей записи блога я обещал рассказать, почему сложно демонстрировать 64\-битные ошибки на простых примерах\. Разговор касался operator\[\] и я говорил, что в простых случая может работать даже явно некорректный код\.

Сейчас я приведу такой пример:

```cpp
class MyArray
{
public:
  char *m_p;
  size_t m_n;
  MyArray(const size_t n)
  {
    m_n = n;
    m_p = new char[n];
  }
  ~MyArray() { delete [] m_p; }
  char &operator[](int index)
    { return m_p[index]; }
  char &operator()(ptrdiff_t index)
    { return m_p[index]; }
  ptrdiff_t CalcSum()
  {
    ptrdiff_t sum = 0;
    for (size_t i = 0; i != m_n; ++i)
      sum += m_p[i];
    return sum;
  }
};
void Test()
{
  ptrdiff_t a = 2560;
  ptrdiff_t b = 1024;
  ptrdiff_t c = 1024;
  MyArray array(a * b * c);
  for (ptrdiff_t i = 0; i != a * b * c; ++i)
    array(i) = 1;
  ptrdiff_t sum1 = array.CalcSum();
  for (int i = 0; i != a * b * c; ++i)
    array[i] = 2;
  ptrdiff_t sum2 = array.CalcSum();
  if (sum1 != sum2 / 2)
    MessageBox(NULL, _T("Normal error"),
        _T("Test"), MB_OK);
  else
    MessageBox(NULL, _T("Fantastic"),
        _T("Test"), MB_OK);
}
```

Вот кратко, что делает этот код:

1. Создает массив размером 2\.5 гигабайта \(более INT\_MAX элементов\)\.
1. Заполняет массив единицами, используя корректный operator\(\) с параметром ptrdiff\_t\.
1. Считает сумму всех элементов и помещает ее в переменную sum1\.
1. Заполняет массив двойками, используя некорректный operator\[\] с параметром int\. Теоретически int не позволяет нам обратиться к элементам с номерами более чем INT\_MAX\. Есть и вторая ошибка, допущенная в цикле "for \(int i \= 0; i \!\= a \* b \* c; \+\+i\)"\. В качестве индекса мы также используем int\. Эта двойная ошибка сделана, чтобы компилятор не выдавал предупреждений о преобразовании 64\-битного значения в 32\-битное\. Фактически должно произойти переполнение и обращение к элементу с отрицательным номером, что повлечет аварийное завершение программы\. Это кстати и происходит в debug\-версии\.
1. Считает сумму всех элементов и помещает ее в переменную sum2\.
1. Если \(sum1 \=\= sum2 / 2\), то произошло невозможное, и выводится сообщение "Fantastic"\.

Несмотря на две ошибки в приведенном коде он успешно работает в 64\-битном release\-варианте и выводит сообщение "Fantastic"\!

Давайте теперь разберемся почему\. Дело в том, что компилятор угадал наше желание заполнить массив значением 1 и 2\. И в обоих случаях оптимизировал наш код с использованием вызова функции memset:

![0047_Optimization_in_the_world_of_64-bit_errors_ru/image1.png](https://import.viva64.com/docx/blog/0047_Optimization_in_the_world_of_64-bit_errors_ru/image1.png)

Вывод первый \- компилятор в плане оптимизации кода молодец\. Вывод второй \- следует не терять бдительность\.

Данная ошибка может быть легко обнаружена в debug\-версии, где оптимизации нет, и код, записывающий двойки в массив, приводит к аварийному завершению\. Опасность в том, что данный код некорректно ведет себя только при работе с большими массивами\. Скорее всего, обработка более двух миллиардов элементов будет отсутствовать в юнит\-тестах, запускаемых для отладочной версией\. А release\-версия может долго хранить в тайне эту ошибку\. Это ошибка может совершенно неожиданно проявиться при малейшем изменении кода\. Посмотрите, что произойдет, если ввести одну дополнительную переменную n:

```cpp
void Test()
{
  ptrdiff_t a = 2560;
  ptrdiff_t b = 1024;
  ptrdiff_t c = 1024;
  ptrdiff_t n = a * b * c;
  MyArray array(n);
  for (ptrdiff_t i = 0; i != n; ++i)
    array(i) = 1;
  ptrdiff_t sum1 = array.CalcSum();
  for (int i = 0; i != n; ++i)
    array[i] = 2;
  ptrdiff_t sum2 = array.CalcSum();
  ...
}
```

В этот раз release\-версия аварийно завершится\. Посмотрим на ассемблерный код\.

![0047_Optimization_in_the_world_of_64-bit_errors_ru/image2.png](https://import.viva64.com/docx/blog/0047_Optimization_in_the_world_of_64-bit_errors_ru/image2.png)

Для корректного operator\(\) компилятор вновь построил код с вызовом memset\. Эта часть, как и раньше, отлично работает\. А вот в коде, где используется operator\[\], происходит выход за рамки массива, так как условие "i \!\= n" не выполняется\. Это не совсем тот код, который я хотел создать, но в простом коде это очень сложно реализовать, а большой отрывок кода сложно рассматривать\. В любом случае это не меняет сути\. Код начал аварийно завершаться, как это и должно быть\.

Почему я так много времени посвятил данной тематике? Видимо меня мучает проблема, что я не могу демонстрировать 64\-битные ошибки на простых примерах\. Я пишу что\-то простое для демонстрации, но обидно, что если это действительно попробовать, то оно работает в release\-варианте\. А следовательно ошибки как бы и нет\. Но они есть и очень коварны и сложны в обнаружении\. Специально повторюсь\. Подобные ошибки легко пропустить при отладке и прогоне юнит\-тестов на debug версии\. Редко у кого хватить терпения отлаживать программу или ждать тесты, когда они обрабатывают гигабайты\. Release\-версия может пройти большое серьезное тестирование\. А следующая сборка, где что\-то незначительно поправлено, или используется новая версия компилятора будет неработоспособна на большом объеме данных\.

По поводу диагностики данной ошибки смотрите предыдущий [пост](https://pvs-studio.ru/ru/blog/posts/cpp/0046/), где описывается новое предупреждение [V302](https://pvs-studio.ru/ru/docs/warnings/v302/)\.