﻿# Магические константы и функция malloc\(\)

Вновь хочется вернуться к вопросу использования магических констант в коде\. Можно сколько угодно говорить, что для вычисления корректного размера выделяемой памяти необходимо использовать оператор sizeof\(\)\. Но эти знания и корректное написание нового кода не помогут в диагностировании уже существующей ошибки в дебрях старого кода в больших проектах\.

Приведем классический пример ошибки:

```cpp
size_t nCount = 10;
int **poinerArray = (int **)malloc(nCount * 4);
```

Код некорректен, но в рамках одной 32\-битной системы он будет стабильно работать\. Ошибка может проявить себя при адаптации программы к другой программной/аппаратной среде\. Важность обнаружения подобного кода стала по настоящему актуальна в связи с массовым переносом программного обеспечения на 64\-битные системы\. Изменение размеров ряда базовых типов делает код, аналогичный выше приведенному, крайне опасным\.

На приведенный выше код статический анализатор Viva64, входящий в состав [PVS\-Studio](https://pvs-studio.ru/ru/pvs-studio/), выдаст предупреждение об использование магической константы "4″ и ошибка будет обнаружена в ходе просмотра диагностических сообщений\. Но код может быть более запутан:

```cpp
#define N_COUNT 100
#define POINTER_SIZE 4
#define NSIZE (N_COUNT * POINTER_SIZE)
int **pArray = (int **)malloc(NSIZE);
```

Ошибку в подобном коде, написанном в стиле языка Си с использованием \#define, диагностировать сложнее\. Хотя в коде присутствует константа 4, заданная через макрос, в анализаторе Viva64 сознательно заблокирован вывод предупреждения на подобные конструкции\. Анализатор игнорирует магические константы, задаваемые макросами \(\#define\) по двум причинам\. Во\-первых, если программист задает константы через макросы, то, скорее всего, он знает, что делает и очень высока вероятность ложного срабатывания\. Во\-вторых, если реагировать на опасные с точки зрения 64\-битности константы \(4, 8, 32 и т\.д\.\), то возникнет лавина ложных срабатываний связанная с использованием Windows API\. В качестве примера приведем безобиднейший код:

```cpp
MessageBox("Are you sure ?",
           "Some question",
           MB_YESNO | MB_ICONQUESTION);
```

Если анализировать магические числа, скрывающиеся за макросами MB\_YESNO и MB\_ICONQUESTION, то на приведенную строчку необходимо выдать два предупреждения об использовании магических констант 4 и 32\. Естественно это будет неприемлемо высоким уровнем ложных срабатываний\.

Можно при анализе функции malloc\(\) выводить информацию о всех опасных магических константах, независимо, макрос это или нет\. Но и этого будет недостаточно для следующего случая:

```cpp
int **pArray = (int **)malloc(400);
```

Если пойти дальше и считать любое число, используемое в выражении для функции malloc\(\) опасным, то это тоже повлечет к ложным срабатываниям на корректном коде:

```cpp
int **pArray = (int **)malloc(400 * sizeof(int *));
```

Проанализировав ситуацию, мы решили реализовать новое правило, для верификации выражений, результат которых передается в функцию malloc\(\)\. На данный момент правило сформулировано следующим образом:

_Опасным следует считать использование числовых литералов в выражении, передаваемом в функцию malloc\(\)\._

_Исключения:_

_1\) В выражении присутствует оператор sizeof\(\)_

_2\) Все числовые литералы делятся на 4 c остатком_

Благодаря данному правилу мы сможем предупредить об ошибке в следующем коде:

 1\) Первый пример:

```cpp
void *p = malloc(nCount * 4);
```

2\) Второй пример:

```cpp
#define N_COUNT 100
#define POINTER_SIZE 4
#define NSIZE (N_COUNT * POINTER_SIZE)
int **pArray = (int **)malloc(NSIZE);
```

А также не выдать ложное предупреждение на код вида:

 1\) Первый пример:

```cpp
void *p = malloc(sizeof(double) * 4);
```

2\) Второй пример:

```cpp
#define N_COUNT 100
#define POINTER_SIZE sizeof(int *)
#define NSIZE (N_COUNT * POINTER_SIZE)
int **pArray = (int **)malloc(NSIZE);
```

Скорее всего, это новое диагностическое правило появится в следующей версии PVS\-Studio 3\.30\.

Рассмотрим теперь другую ситуацию, также связанную с функцией malloc\(\) и неверным предположением о выравнивании данных\. Это не совсем связано с магическими числами, но проблема очень родственная\. Рассмотрим пример кода:

```cpp
struct MyBigStruct {
  unsigned m_numberOfPointers;
  void *m_Pointers[1];
};
unsigned n = 10000;
void *ptr = malloc(sizeof(unsigned) +
                   n * sizeof(void *));
```

Хотя в данном коде не используется магических чисел, размер типов определяется через sizeof\(\), код все равно не корректен\. В нем неучтено изменение способа выравнивания данных, различного для 32\-битных и 64\-битных систем\. Корректным будет следующий код:

```cpp
void *ptr = malloc(
  offsetof(MyBigStruct, m_Pointers) +
  n * sizeof(void *));
```

Чтобы предупредить пользователя о возможной допущенной ошибке мы планируем реализовать еще одно правило:

Опасным следует считать использование более одного оператора sizeof\(\) в выражении, передаваемом в функцию malloc\. Возможно, при вычислении размера структуры не учитывается изменение выравнивания\.

В ряде случаев такое правило будет давать ложные срабатывания, но подобные места в любом случае стоит тщательно проверить\.

Описанные выше опасные выражения с участием магических констант актуальны не только для функции malloc\(\), но и для класса таких функций как fread, fwrite и так далее\. Но эти функции требуют отдельного изучения, и к их анализу мы приступим позже, когда отработаем диагностику, связанную с malloc\(\)\.