metrica
Мы используем куки, чтобы пользоваться сайтом было удобно.
Хорошо
to the top
close form

Заполните форму в два простых шага ниже:

Ваши контактные данные:

Шаг 1
Поздравляем! У вас есть промокод!

Тип желаемой лицензии:

Шаг 2
Team license
Enterprise license
** Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности
close form
Запросите информацию о ценах
Новая лицензия
Продление лицензии
--Выберите валюту--
USD
EUR
RUB
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
Бесплатная лицензия PVS‑Studio для специалистов Microsoft MVP
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
Для получения лицензии для вашего открытого
проекта заполните, пожалуйста, эту форму
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
Мне интересно попробовать плагин на:
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
check circle
Ваше сообщение отправлено.

Мы ответим вам на


Если вы так и не получили ответ, пожалуйста, проверьте папку
Spam/Junk и нажмите на письме кнопку "Не спам".
Так Вы не пропустите ответы от нашей команды.

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

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

11 Янв 2010

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

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

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);
}

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

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

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

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

0047_Optimization_in_the_world_of_64-bit_errors_ru/image1.png

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

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

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

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

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

По поводу диагностики данной ошибки смотрите предыдущий пост, где описывается новое предупреждение V302.

Популярные статьи по теме


Комментарии (0)

Следующие комментарии next comments
close comment form