Урок 9. Паттерн 1. Магические числа

502 views

Published on

В некачественном коде часто встречаются магические числовые константы, наличие которых опасно само по себе. При миграции кода на 64-битную платформу эти константы могут сделать код неработоспособным, если участвуют в операциях вычисления адреса, размера объектов или в битовых операциях.

  • Be the first to comment

  • Be the first to like this

Урок 9. Паттерн 1. Магические числа

  1. 1. Урок 9. Паттерн 1. Магические числаВ некачественном коде часто встречаются магические числовые константы, наличие которыхопасно само по себе. При миграции кода на 64-битную платформу эти константы могут сделатькод неработоспособным, если участвуют в операциях вычисления адреса, размера объектов или вбитовых операциях.В таблице 1 перечислены основные магические константы, которые могут влиять наработоспособность приложения на новой платформе. Таблица 1 - Основные магические значения, опасные при переносе приложений с 32-битной на 64-битную платформуСледует внимательно изучить код на предмет наличия магических констант и заменить ихбезопасными константами и выражениями. Для этого можно использовать оператор sizeof(),специальные значения из <limits.h>, <inttypes.h> и так далее.Приведем несколько ошибок, связанных с использованием магических констант. Самойраспространенной является запись в виде числовых значений размеров типов:1) size_t ArraySize = N * 4; intptr_t *Array = (intptr_t *)malloc(ArraySize);2) size_t values[ARRAY_SIZE]; memset(values, 0, ARRAY_SIZE * 4);3) size_t n, r; n = n >> (32 - r);Во всех случаях предполагаем, что размер используемых типов всегда равен 4 байта. Исправлениекода заключается в использовании оператора sizeof():
  2. 2. 1) size_t ArraySize = N * sizeof(intptr_t); intptr_t *Array = (intptr_t *)malloc(ArraySize);2) size_t values[ARRAY_SIZE]; memset(values, 0, ARRAY_SIZE * sizeof(size_t));или memset(values, 0, sizeof(values)); //preferred alternative3) size_t n, r; n = n >> (CHAR_BIT * sizeof(n) - r);Иногда может потребоваться специфическая константа. В качестве примера мы возьмем значениеsize_t, где все биты кроме 4 младших должны быть заполнены единицами. В 32-битнойпрограмме эта константа может быть объявлена следующим образом:// constant 1111..110000const size_t M = 0xFFFFFFF0u;Это некорректный код в случае 64-битной системы. Такие ошибки очень неприятны, так как записьмагических констант может быть осуществлена различными способами и их поиск достаточнотрудоемок. К сожалению, нет никаких других путей, кроме как найти и исправить этот код,используя директиву #ifdef или специальный макрос.#ifdef _WIN64 #define CONST3264(a) (a##i64)#else #define CONST3264(a) (a)#endifconst size_t M = ~CONST3264(0xFu);Иногда в качестве кода ошибки или другого специального маркера используют значение "-1",записывая его как "0xffffffff". На 64-битной платформе записанное выражение некорректно иследует явно использовать значение -1. Пример некорректного кода, использующего значение0xffffffff как признак ошибки:#define INVALID_RESULT (0xFFFFFFFFu)size_t MyStrLen(const char *str) { if (str == NULL) return INVALID_RESULT; ... return n;
  3. 3. }size_t len = MyStrLen(str);if (len == (size_t)(-1)) ShowError();На всякий случай уточним, чему равно значение "(size_t)(-1)" на 64-битной платформе. Можноошибиться, назвав значение 0x00000000FFFFFFFFu. Согласно правилам языка Си++ сначалазначение -1 преобразуется в знаковый эквивалент большего типа, а затем в беззнаковоезначение:int a = -1; // 0xFFFFFFFFi32ptrdiff_t b = a; // 0xFFFFFFFFFFFFFFFFi64size_t c = size_t(b); // 0xFFFFFFFFFFFFFFFFui64Таким образом, "(size_t)(-1)" на 64-битной архитектуре представляется значением0xFFFFFFFFFFFFFFFFui64, которое является максимальным значением для 64-битного типа size_t.Вернемся к ошибке с INVALID_RESULT. Использование константы 0xFFFFFFFFu приводит кневыполнению условия "len == (size_t)(-1)" в 64-битной программе. Наилучшее решениезаключается в изменении кода так, чтобы специальных маркерных значений не требовалось. Еслипо какой-то причине Вы не можете от них отказаться или считаете нецелесообразнымсущественные правки кода, то просто используйте честное значение -1.#define INVALID_RESULT (size_t(-1))...Приведем еще один пример связанный с использованием 0xFFFFFFFF. Код взят из реальногоприложения для трёхмерного моделирования:hFileMapping = CreateFileMapping ( (HANDLE) 0xFFFFFFFF, NULL, PAGE_READWRITE, (DWORD) 0, (DWORD) (szBufIm), (LPCTSTR) &FileShareNameMap[0]);Как вы уже правильно догадались, 0xFFFFFFFF здесь также приведет к ошибке на 64-битнойсистеме. Первый аргумент функции CreateFileMapping может иметь значениеINVALID_HANDLE_VALUE, объявленное следующим образом:#define INVALID_HANDLE_VALUE ((HANDLE)(LONG_PTR)-1)
  4. 4. В результате INVALID_HANDLE_VALUE действительно совпадает в 32-битной системе со значением0xFFFFFFFF. А вот в 64-битной системе в функцию CreateFileMapping будет передано значение0x00000000FFFFFFFF, в результате чего система посчитает аргумент некорректным и вернет кодошибки. Причина в том, что значение 0xFFFFFFFF имеет БЕЗЗНАКОВЫЙ тип (unsigned int). Значение0xFFFFFFFF не помещается в тип int и поэтому является типом unsigned. Это тонкий момент, накоторый следует обратить внимание при переходе на 64-битные системы. Поясним его напримере:void foo(void *ptr){ cout << ptr << endl;}int _tmain(int, _TCHAR *[]){ cout << "-1tt"; foo((void *)-1); cout << "0xFFFFFFFFt"; foo((void *)0xFFFFFFFF);}Результат работы 32-битного варианта программы:-1 FFFFFFFF0xFFFFFFFF FFFFFFFFРезультат работы 64-битного варианта программы:-1 FFFFFFFFFFFFFFFF0xFFFFFFFF 00000000FFFFFFFFДиагностикаСтатический анализатор PVS-Studio предупреждает о наличии в коде магических констант,имеющих наибольшую опасность при создании 64-битного приложения. Для этого используютсядиагностические сообщения V112 и V118. Учтите, анализатор сознательно не предупреждает опотенциальной ошибке, если магическая константа определена через макрос. Пример:#define MB_YESNO 0x00000004LMessageBox("Are you sure ?", "Question", MB_YESNO);Если совсем кратко, то причина такого поведения - защита от огромного количества ложныхсрабатываний. При этом считается, что если программист задает константу через макрос, то он
  5. 5. делает это специально, чтобы подчеркнуть ее безопасность. Подробнее с данным вопросомможно познакомиться в записи блога на нашем сайте "Магические константы и функция malloc()".Авторы курса: Андрей Карпов (karpov@viva64.com), Евгений Рыжков (evg@viva64.com).Правообладателем курса "Уроки разработки 64-битных приложений на языке Си/Си++"является ООО "Системы программной верификации". Компания занимается разработкойпрограммного обеспечения в области анализа исходного кода программ. Сайт компании:http://www.viva64.com.Контактная информация: e-mail: support@viva64.com, 300027, г. Тула, а/я 1800.

×