Урок 17. Паттерн 9. СмешаннаяарифметикаНадеемся, вы уже успели отдохнуть от 13 урока и теперь сможете рассмотреть еще один...
int y = 100000;int z = 100000;ptrdiff_t size = 1;                                // Result:ptrdiff_t v1 = x * y * z;      ...
ptrdiff_t v2 = ((ptrdiff_t (a) * b) * c()) * d();В результате каждый из операндов перед умножением будет преобразовываться...
Рисунок 2 - Преобразования, происходящие в 64-битном кодеЕсли вам необходимо вернуть прежнее поведение кода - следует изме...
+ z * Width * Height" вычисляется с использованием типа int. Мы думаем, вы уже догадались, чтоисправленный код будет выгля...
Этот код не напоминает вам пример с некорректной арифметикой указателей, рассмотренный в13-том уроке? Да, здесь происходит...
программного обеспечения в области анализа исходного кода программ. Сайт компании:http://www.viva64.com.Контактная информа...
Upcoming SlideShare
Loading in …5
×

Урок 17. Паттерн 9. Смешанная арифметика

262 views

Published on

Надеемся, вы уже успели отдохнуть от 13 урока и теперь сможете рассмотреть еще один важный паттерн ошибок, связанный с арифметическими выражениями, в которых участвуют типы различной размерности.

Published in: Technology, Business
  • Be the first to comment

  • Be the first to like this

Урок 17. Паттерн 9. Смешанная арифметика

  1. 1. Урок 17. Паттерн 9. СмешаннаяарифметикаНадеемся, вы уже успели отдохнуть от 13 урока и теперь сможете рассмотреть еще один важныйпаттерн ошибок, связанный с арифметическими выражениями, в которых участвуют типыразличной размерности.Смешанное использование memsize- и не memsize-типов в выражениях может приводить кнекорректным результатам на 64-битных системах и быть связано с изменением диапазонавходных значений. Рассмотрим ряд примеров:size_t Count = BigValue;for (unsigned Index = 0; Index != Count; ++Index){ ... }Это пример вечного цикла, если Count > UINT_MAX. Предположим, что на 32-битных системах этоткод работал с количеством итераций менее значения UINT_MAX. Но 64-битный вариантпрограммы может обрабатывать больше данных, и ему может потребоваться большее количествоитераций. Поскольку значения переменной Index лежат в диапазоне [0..UINT_MAX], то условие"Index != Count" никогда не выполнится, что и приводит к бесконечному циклу.Примечание. Заметим, что при определенных настройках компилятора данный пример можетуспешно отработать. Из-за этого иногда, кажется, что код корректен, и возникаетпутаница. В одном из последующих уроков мы расскажем о фантомных ошибках, проявляющихсебя только временами. Если вам интересно уже сейчас понять это странное поведение кода,то предлагаем ознакомиться со статьей "64-битный конь, который умеет считать".Для исправления кода необходимо использовать в выражениях только memsize-типы. В данномпримере можно заменить тип переменной Index с unsigned на size_t.Другая часто встречающаяся ошибка - запись выражений следующего вида:int x, y, z;ptrdiff_t SizeValue = x * y * z;Ранее уже рассматривались подобные примеры, когда при вычислении значений сиспользованием не memsize-типов происходило арифметическое переполнение. И конечныйрезультат был некорректен. Поиск и исправление приведенного кода осложняется тем, чтокомпиляторы, как правило, не выдают на него никаких предупреждений. С точки зрения языкаСи++ это совершенно корректная конструкция. Происходит умножение нескольких переменныхтипа int, после чего результат неявно расширяется до типа ptrdiff_t и происходит присваивание.Приведем небольшой код, показывающий опасность неаккуратных выражений со смешаннымитипами (результаты получены с использованием Microsoft Visual C++ 2005, 64-битный режимкомпиляции):int x = 100000;
  2. 2. int y = 100000;int z = 100000;ptrdiff_t size = 1; // Result:ptrdiff_t v1 = x * y * z; // -1530494976ptrdiff_t v2 = ptrdiff_t (x) * y * z; // 1000000000000000ptrdiff_t v3 = x * y * ptrdiff_t (z); // 141006540800000ptrdiff_t v4 = size * x * y * z; // 1000000000000000ptrdiff_t v5 = x * y * z * size; // -1530494976ptrdiff_t v6 = size * (x * y * z); // -1530494976ptrdiff_t v7 = size * (x * y) * z; // 141006540800000ptrdiff_t v8 = ((size * x) * y) * z; // 1000000000000000ptrdiff_t v9 = size * (x * (y * z)); // -1530494976Необходимо чтобы все операнды в подобных выражениях были приведены в процессевычисления к типу большей разрядности. Помните, что выражение видаptrdiff_t v2 = ptrdiff_t (x) + y * z;вовсе не гарантирует правильный результат. Оно гарантирует только то, что выражение " ptrdiff_t(x) + y * z" будет иметь тип ptrdiff_t.Следовательно, если результатом выражения должен являться memsize-тип, то в выражениидолжны участвовать только memsize-типы. Или элементы, приведенные к memsize-типам.Правильный вариант:ptrdiff_t v2 = ptrdiff_t (x) + ptrdiff_t (y) * ptrdiff_t (z); // OK!Впрочем не всегда необходимо приводить все аргументы к memsize-типу. Если выражениесостоит из одинаковых операторов, то достаточно привести к memsize-типу только первыйаргумент. Рассмотрим пример:int c();int d();int a, b;ptrdiff_t v2 = ptrdiff_t (a) * b * c() * d();Порядок вычисления выражения с операторами одинакового приоритета не определен. Точнее,компилятор волен вычислять подвыражения, например вызов функций c() и d() в том порядке,который он считает более эффективным, даже если подвыражения вызывают побочные эффекты.Порядок возникновения побочных эффектов не определен. Но поскольку операция умноженияотносится к лево-ассоциативным операторам, то вычисление будет происходить следующимобразом:
  3. 3. ptrdiff_t v2 = ((ptrdiff_t (a) * b) * c()) * d();В результате каждый из операндов перед умножением будет преобразовываться к типу ptrdiff_t имы получим корректный результат.Примечание. Если у вас есть целочисленные вычисления, для которых крайне важен контрольнад переполнениями, то мы предлагаем обратить внимание на класс SafeInt, реализацию иописание которого можно найти в MSDN.Смешанное использование типов может проявляться и в изменении программной логики:ptrdiff_t val_1 = -1;unsigned int val_2 = 1;if (val_1 > val_2) printf ("val_1 is greater than val_2n");else printf ("val_1 is not greater than val_2n");//Output on 32-bit system: "val_1 is greater than val_2"//Output on 64-bit system: "val_1 is not greater than val_2"На 32-битной системе переменная val_1 согласно правилам языка Си++ расширялась до типаunsigned int и становилась значением 0xFFFFFFFFu. В результате условие "0xFFFFFFFFu > 1"выполнялось. На 64-битной системе наоборот расширяется переменная val_2 до типа ptrdiff_t. Вэтом случае уже проверяется выражение "-1 > 1". На рисунке 1 и 2 схематично отображеныпроисходящие преобразования. Рисунок 1 - Преобразования, происходящие в 32-битном коде
  4. 4. Рисунок 2 - Преобразования, происходящие в 64-битном кодеЕсли вам необходимо вернуть прежнее поведение кода - следует изменить тип переменной val_2:ptrdiff_t val_1 = -1;size_t val_2 = 1;if (val_1 > val_2) printf ("val_1 is greater than val_2n");else printf ("val_1 is not greater than val_2n");Правильнее вообще не сравнивать знаковые и беззнаковые типы, но это выходит за рамкиобсуждаемой темы.Мы рассмотрели только простые выражения. Но описываемые проблемы могут проявиться и прииспользовании других конструкций языка Си++:extern int Width, Height, Depth;size_t GetIndex(int x, int y, int z) { return x + y * Width + z * Width * Height;}...MyArray[GetIndex(x, y, z)] = 0.0f;В случае работы с большими массивами (более INT_MAX элементов) данный код будет вести себянекорректно, и мы будем адресоваться не к тем элементам массива MyArray, к которымрассчитываем. Несмотря на то, что мы возвращаем значение типа size_t, выражение "x + y * Width
  5. 5. + z * Width * Height" вычисляется с использованием типа int. Мы думаем, вы уже догадались, чтоисправленный код будет выглядеть следующим образом:extern int Width, Height, Depth;size_t GetIndex(int x, int y, int z) { return (size_t)(x) + (size_t)(y) * (size_t)(Width) + (size_t)(z) * (size_t)(Width) * (size_t)(Height);}Или чуть более просто:extern int Width, Height, Depth;size_t GetIndex(int x, int y, int z) { return (size_t)(x) + (size_t)(y) * Width + (size_t)(z) * Widt) * Height;}В следующем примере, у нас вновь смешивается memsize-тип (указатель) и 32-битный типunsigned:extern char *begin, *end;unsigned GetSize() { return end - begin;}Результат выражения "end - begin" имеет тип ptrdiff_t. Поскольку функция возвращает типunsigned, то происходит неявное приведение типа, при котором старшие биты результататеряются. Таким образом, если указатели begin и end ссылаются на начало и конец массива, поразмеру большего UINT_MAX (4Gb), то функция вернет некорректное значение.И еще один пример. На этот раз рассмотрим не возвращаемое значение, а формальный аргументфункции:void foo(ptrdiff_t delta);int i = -2;unsigned k = 1;foo(i + k);
  6. 6. Этот код не напоминает вам пример с некорректной арифметикой указателей, рассмотренный в13-том уроке? Да, здесь происходит то же самое. Некорректный результат возникает при неявномрасширении фактического аргумента, имеющего значение 0xFFFFFFFF и тип unsigned, до типаptrdiff_t.ДиагностикаОшибки, возникающие на 64-битных системах при смешанном использование простыхцелочисленных типов и memsize-типов, представлены большим количеством синтаксическихконструкций языка Си++. Для диагностики этих ошибок используется целый ряд диагностическихсообщений. Анализатор PVS-Studio предупреждает о потенциально возможных ошибках,используя следующие сообщения: V101, V103, V104, V105, V106, V107, V109, V110, V121.Вернемся к ранее рассмотренному примеру:int c();int d();int a, b;ptrdiff_t x = ptrdiff_t(a) * b * c() * d();Хотя само выражение перемножает аргументы, расширяя их тип до ptrdiff_t, ошибка можетсодержаться в вычислении самих этих аргументов. Поэтому анализатор все равно предупреждаето смешивании типов: "V104: Implicit type conversion to memsize type in an arithmetic expression".Также инструмент PVS-Studio позволяет найти потенциально опасные выражения, которыескрываются за явным приведением типов. Для этого можно включить в настройках анализаторапредупреждения V201 и V202. По умолчанию анализатор не выдает предупреждения связанные сприведением типа, в случае, когда приведение осуществляется явно. Пример:TCHAR *begin, *end;unsigned size = static_cast<unsigned>(end - begin);Выявить подобный некорректный код и позволяют сообщения V201 и V202.При этом анализатор не обратит внимания на безопасные с точки зрения 64-битного кодаприведения типов:const int *constPtr;int *ptr = const_cast<int>(constPtr);float f = float(constPtr[0]);char ch = static_cast<char>(sizeof(double));Авторы курса: Андрей Карпов (karpov@viva64.com), Евгений Рыжков (evg@viva64.com).Правообладателем курса "Уроки разработки 64-битных приложений на языке Си/Си++"является ООО "Системы программной верификации". Компания занимается разработкой
  7. 7. программного обеспечения в области анализа исходного кода программ. Сайт компании:http://www.viva64.com.Контактная информация: e-mail: support@viva64.com, 300027, г. Тула, а/я 1800.

×