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

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

В таблице 1 перечислены основные магические константы, которые могут влиять на работоспособность приложения на новой платформе\.

![09_Pattern_01_Magic_numbers_ru/image2.png](https://import.viva64.com/docx/blog/09_Pattern_01_Magic_numbers_ru/image2.png)

_Таблица 1 \- Основные магические значения, опасные при переносе приложений с 32\-битной на 64\-битную платформу_

Следует внимательно изучить код на предмет наличия магических констант и заменить их безопасными константами и выражениями\. Для этого можно использовать оператор sizeof\(\), специальные значения из <limits\.h\>, <inttypes\.h\> и так далее\.

Приведем несколько ошибок, связанных с использованием магических констант\. Самой распространенной является запись в виде числовых значений размеров типов:

```cpp
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\(\):

```cpp
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));
```

или

```cpp
   memset(values, 0, sizeof(values)); //preferred alternative
3) size_t n, r;
   n = n >> (CHAR_BIT * sizeof(n) - r);
```

Иногда может потребоваться специфическая константа\. В качестве примера мы возьмем значение [size\_t](https://pvs-studio.ru/ru/blog/terms/0044/), где все биты, кроме 4 младших, должны быть заполнены единицами\. В 32\-битной программе эта константа может быть объявлена следующим образом:

```cpp
// constant '1111..110000'
const size_t M = 0xFFFFFFF0u;
```

Это некорректный код в случае 64\-битной системы\. Такие ошибки очень неприятны, так как запись магических констант может быть осуществлена различными способами и их поиск достаточно трудоемок\. К сожалению, нет никаких других путей, кроме как найти и исправить этот код, используя директиву \#ifdef или специальный макрос\.

```cpp
#ifdef _WIN64
  #define CONST3264(a) (a##i64)
#else
  #define CONST3264(a)  (a)
#endif
const size_t M = ~CONST3264(0xFu);
```

Иногда в качестве кода ошибки или другого специального маркера используют значение "\-1", записывая его как "0xffffffff"\. На 64\-битной платформе записанное выражение некорректно, и следует явно использовать значение \-1\. Пример некорректного кода, использующего значение 0xffffffff, как признак ошибки:

```cpp
#define INVALID_RESULT (0xFFFFFFFFu)
size_t MyStrLen(const char *str) {
  if (str == NULL)
    return INVALID_RESULT;
  ...
  return n;
}
size_t len = MyStrLen(str);
if (len == (size_t)(-1))
  ShowError();
```

На всякий случай уточним, чему равно значение "\(size\_t\)\(\-1\)" на 64\-битной платформе\. Можно ошибиться, назвав значение 0x00000000FFFFFFFFu\. Согласно правилам языка Си\+\+, сначала значение \-1 преобразуется в знаковый эквивалент большего типа, а затем в беззнаковое значение:

```cpp
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\.

```cpp
#define INVALID_RESULT (size_t(-1))
...
```

Приведем еще один пример, связанный с использованием 0xFFFFFFFF\. Код взят из реального приложения для трёхмерного моделирования:

```cpp
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\)

В результате INVALID\_HANDLE\_VALUE действительно совпадает в 32\-битной системе со значением 0xFFFFFFFF\. А вот в 64\-битной системе в функцию CreateFileMapping будет передано значение 0x00000000FFFFFFFF, в результате чего система посчитает аргумент некорректным и вернет код ошибки\. Причина в том, что значение 0xFFFFFFFF имеет БЕЗЗНАКОВЫЙ тип \(unsigned int\)\. Значение 0xFFFFFFFF не помещается в тип int и поэтому является типом unsigned\. Это тонкий момент, на который следует обратить внимание при переходе на 64\-битные системы\. Поясним его на примере:

```cpp
void foo(void *ptr)
{
  cout << ptr << endl;
}
int _tmain(int, _TCHAR *[])
{
  cout << "-1\t\t";
  foo((void *)-1);
  cout << "0xFFFFFFFF\t";
  foo((void *)0xFFFFFFFF);
}
```

Результат работы 32\-битного варианта программы:

```cpp
-1              FFFFFFFF
0xFFFFFFFF      FFFFFFFF
```

Результат работы 64\-битного варианта программы:

```cpp
-1              FFFFFFFFFFFFFFFF
0xFFFFFFFF      00000000FFFFFFFF
```

## Диагностика

Статический анализатор [PVS\-Studio](https://pvs-studio.ru/ru/pvs-studio/) предупреждает о наличии в коде магических констант, имеющих наибольшую опасность при создании 64\-битного приложения\. Для этого используются диагностические сообщения [V112](https://pvs-studio.ru/ru/docs/warnings/v112/) и [V118](https://pvs-studio.ru/ru/docs/warnings/v118/)\. Учтите: анализатор сознательно не предупреждает о потенциальной ошибке, если магическая константа определена через макрос\. Пример:

```cpp
#define MB_YESNO  0x00000004L
MessageBox("Are you sure ?", "Question", MB_YESNO);
```

Если совсем кратко, то причина такого поведения \- защита от огромного количества ложных срабатываний\. При этом считается, что если программист задает константу через макрос, то он делает это специально, чтобы подчеркнуть ее безопасность\. Подробнее с данным вопросом можно познакомиться в записи блога на нашем сайте "[Магические константы и функция malloc\(\)](https://pvs-studio.ru/ru/blog/posts/cpp/0023/)"\.

_Авторы курса: Андрей Карпов \([karpov@viva64\.com](mailto:karpov@viva64.com)\), Евгений Рыжков \([evg@viva64\.com](mailto:evg@viva64.com)\)\._

_Правообладателем курса "Уроки разработки 64\-битных приложений на языке Си/Си\+\+" является ООО "Системы программной верификации"\. Компания занимается разработкой программного обеспечения в области анализа исходного кода программ\. [Сайт компании](https://pvs-studio.ru/ru/pvs-studio/)\._