﻿# Почему проверять результат вызова malloc c помощью assert плохая идея

Указатель, который вернула функция malloc, необходимо проверить перед использованием\. Неправильным решением будет использовать для этого макрос assert\. В этой статье мы разберём, почему это является антипаттерном\.

![1104_malloc_assert_ru/image1.png](https://import.viva64.com/docx/blog/1104_malloc_assert_ru/image1.png)

Под функцией [_malloc_](https://en.cppreference.com/w/c/memory/malloc) далее я буду подразумевать не только эту функцию, но и [_calloc_](https://en.cppreference.com/w/c/memory/calloc), [_realloc_](https://en.cppreference.com/w/c/memory/realloc), [_\_aligned\_malloc_](https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/aligned-malloc), [_\_recalloc_](https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/recalloc), [_strdup_](https://en.cppreference.com/w/c/experimental/dynamic/strdup) и так далее\.

Функция _malloc_ возвращает нулевой указатель, если невозможно выделить буфер памяти указанного размера\. Поэтому прежде, чем разыменовать указатель, его нужно проверить на равенство _NULL_\.

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

Классическая проверка выглядит следующим образом:

```cpp
int *ptr = malloc(sizeof(int) * N);
if (!ptr)
{
  // Обработка ошибки выделения памяти
}
```

Хотя мне больше нравится явный вариант сравнения\. Такой стиль дополнительно защищает код от опечаток и делает его чуть понятнее \(сразу очевидно, что проверяется указатель\):

```cpp
if (ptr == NULL)
```

Главное, ошибка выделения памяти должна быть обработана\. Это может быть:

* Завершение работы программы с возвратом кода ошибки \(если такое поведение уместно\);
* Отказ выполнения операции, но продолжение работы, чтобы дать сохранить данные;
* Если программа управляет неким технологическим устройством, то его выключение \(если такое поведение уместно\);
* Библиотека должна вернуть статус ошибки, а уже приложение будет решать, как себя вести;
* Другие варианты, зависящие от типа программы\.

Не следует предполагать, что проверку можно пропустить, так как при разыменовании нулевого указателя программа в любом случае аварийно завершится\. Она может и не завершиться\. Разыменование нулевого указателя — это неопределённое поведение, и его проявления могут быть очень неожиданными для программиста\. Про всё это как раз написано в уже упомянутой ранее [статье](https://pvs-studio.ru/ru/blog/posts/cpp/0938/)\.

Теперь поговорим про этот антипаттерн:

```cpp
int *ptr = malloc(sizeof(int) * N);
assert(ptr);
memcpy(ptr, foo, sizeof(int) * N);
```

Для проверки указателя некоторые программисты используют макрос [_assert_](https://en.cppreference.com/w/cpp/error/assert)\. Или его аналоги, например макрос _ASSERT_ из библиотеки MFC\.

Важно\. Если объявлен макрос _NDEBUG_, то макрос _assert_ выключается \(ничего не делает\)\. Пример реализации [из assert\.h](https://en.cppreference.com/w/c/error/assert):

```cpp
#ifdef NDEBUG
#define assert(condition) ((void)0)
#else
#define assert(condition) /*implementation defined*/
#endif
```

На практике это означает следующее: в релизной версии программы, которая должна работать максимально быстро, будет объявлен макрос _NDEBUG,_ и _assert_ превратится в "ничто"\. В результате никакой защиты от нулевого указателя нет, и последствия могут быть весьма неприятными\.

В отладочной версии программы _assert_ работает как полагается, но в этом нет практического смысла\.

Обычно при тестировании отладочной версии программы используется простые сценарии работы, не подразумевающие выделения больших объемов памяти\. Скорее всего не произойдёт и сильной фрагментации памяти\. В общем, очень маловероятно, что возникнет ситуация, когда память не сможет быть выделена\.

Если же такое всё\-таки произойдёт, то _assert_ действительно обнаружит нулевой указатель\. Однако для отладочной версии программы в этом мало прока\. В ней разыменование нулевого указателя и так сразу проявит себя\. Там не будет никаких хитрых проблем с неопределённым поведением, как в оптимизированном коде\.

В итоге получается:

* макрос _assert_ не работает там, где нужно \(в релизных сборках\);
* зато работает там, где это мало актуально \(отладочные сборки\)\.

А что, если не объявлять макрос _NDEBUG_ для релизных версий?

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

Не ленитесь писать полноценные проверки аварийных ситуаций, и пользователи будут вам благодарны\. Спасибо за внимание\.

**Дополнительные ссылки:**

1. Масса других способов создать плохой код: [60 антипаттернов для С\+\+ программиста](https://pvs-studio.ru/ru/blog/posts/cpp/1053/)
1. [Разыменовывание нулевого указателя приводит к неопределённому поведению](https://pvs-studio.ru/ru/blog/posts/cpp/0306/)
1. [Следует ли проверять указатель на NULL перед вызовом функции free?](https://pvs-studio.ru/ru/blog/posts/cpp/1100/)