В некачественном коде часто встречаются магические числовые константы, наличие которых опасно само по себе. При миграции кода на 64-битную платформу эти константы могут сделать код неработоспособным, если участвуют в операциях вычисления адреса, размера объектов или в битовых операциях.
Трепещи, мир! Мы выпустили PVS-Studio 4.00 с бесплатным анализатором общего н...
Урок 9. Паттерн 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. 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 alternative
3) size_t n, r;
n = n >> (CHAR_BIT * sizeof(n) - r);
Иногда может потребоваться специфическая константа. В качестве примера мы возьмем значение
size_t, где все биты кроме 4 младших должны быть заполнены единицами. В 32-битной
программе эта константа может быть объявлена следующим образом:
// constant '1111..110000'
const size_t M = 0xFFFFFFF0u;
Это некорректный код в случае 64-битной системы. Такие ошибки очень неприятны, так как запись
магических констант может быть осуществлена различными способами и их поиск достаточно
трудоемок. К сожалению, нет никаких других путей, кроме как найти и исправить этот код,
используя директиву #ifdef или специальный макрос.
#ifdef _WIN64
#define CONST3264(a) (a##i64)
#else
#define CONST3264(a) (a)
#endif
const 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. }
size_t len = MyStrLen(str);
if (len == (size_t)(-1))
ShowError();
На всякий случай уточним, чему равно значение "(size_t)(-1)" на 64-битной платформе. Можно
ошибиться, назвав значение 0x00000000FFFFFFFFu. Согласно правилам языка Си++ сначала
значение -1 преобразуется в знаковый эквивалент большего типа, а затем в беззнаковое
значение:
int a = -1; // 0xFFFFFFFFi32
ptrdiff_t b = a; // 0xFFFFFFFFFFFFFFFFi64
size_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. В результате 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 FFFFFFFF
0xFFFFFFFF FFFFFFFF
Результат работы 64-битного варианта программы:
-1 FFFFFFFFFFFFFFFF
0xFFFFFFFF 00000000FFFFFFFF
Диагностика
Статический анализатор PVS-Studio предупреждает о наличии в коде магических констант,
имеющих наибольшую опасность при создании 64-битного приложения. Для этого используются
диагностические сообщения V112 и V118. Учтите, анализатор сознательно не предупреждает о
потенциальной ошибке, если магическая константа определена через макрос. Пример:
#define MB_YESNO 0x00000004L
MessageBox("Are you sure ?", "Question", MB_YESNO);
Если совсем кратко, то причина такого поведения - защита от огромного количества ложных
срабатываний. При этом считается, что если программист задает константу через макрос, то он
5. делает это специально, чтобы подчеркнуть ее безопасность. Подробнее с данным вопросом
можно познакомиться в записи блога на нашем сайте "Магические константы и функция malloc()".
Авторы курса: Андрей Карпов (karpov@viva64.com), Евгений Рыжков (evg@viva64.com).
Правообладателем курса "Уроки разработки 64-битных приложений на языке Си/Си++"
является ООО "Системы программной верификации". Компания занимается разработкой
программного обеспечения в области анализа исходного кода программ. Сайт компании:
http://www.viva64.com.
Контактная информация: e-mail: support@viva64.com, 300027, г. Тула, а/я 1800.