Avatar ·

В каких случаях использовать malloc, а в каких new в C++?

Приветствую, коллеги! Я изучаю C++ и дошел до управления памятью. Вроде бы разобрался, что malloc — это функция из C, а new — оператор в C++. Но на практике у меня возникла путаница. Допустим, я хочу выделить память под объект класса `Person`. Вот два способа, которые я понимаю: ```cpp #include // для malloc #include class Person { public: Person(const std::string& name) : name_(name) { std::cout << "Person created: " << name_ << std::endl; } ~Person() { std::cout << "Person destroyed: " << name_ << std::endl; } void greet() { std::cout << "Hi, I'm " << name_ << std::endl; } private: std::string name_; }; // Вариант 1: malloc Person* p1 = (Person*)malloc(sizeof(Person)); // Вариант 2: new Person* p2 = new Person("Alice"); ``` Я вижу, что при использовании `new` автоматически вызывается конструктор, а для `malloc` нужно делать placement new или что-то подобное. А что с освобождением? `free(p1)` и `delete p2` — это же не взаимозаменяемо? Меня интересует: **когда конкретно стоит использовать `malloc`, а когда `new`?** Я слышал, что в C++ вообще не рекомендуется использовать `malloc`, так как это небезопасно с точки зрения исключений и конструкторов. Но в коде некоторых старых библиотек (например, работа с сырыми данными или при взаимодействии с C-кодом) `malloc` всё ещё встречается. Объясните, пожалуйста, на примерах, где граница применения каждого подхода. Заранее спасибо! <<>>

Основное правило: используйте new/delete в C++ и избегайте malloc/free

В современном C++ (C++11 и новее) прямой вызов new и delete тоже считается устаревшей практикой — предпочтение отдается умным указателям (std::unique_ptr, std::shared_ptr) и контейнерам STL (например, std::vector). Однако понимание разницы между malloc и new критически важно для глубокого понимания языка.

Ключевые различия между malloc и new

  • Тип возвращаемого значения: malloc возвращает void*, требуется явное приведение типа. new возвращает указатель правильного типа.
  • Вызов конструктора/деструктора: new вызывает конструктор после выделения памяти и деструктор при delete. malloc только выделяет память, не вызывая конструкторы.
  • Размер: malloc требует указать размер в байтах вручную (можно ошибиться). new сам вычисляет размер.
  • Обработка ошибок: При нехватке памяти malloc возвращает NULL, new (по умолчанию) выбрасывает исключение std::bad_alloc.
  • Перегрузка: Оператор new можно перегрузить для конкретного класса. malloc — функция из C, её нельзя перегрузить.

Пример: что происходит под капотом

// malloc — только сырая память
Person* p1 = (Person*)malloc(sizeof(Person));
// p1 указывает на неинициализированную память!
// Вызов p1->greet() — неопределенное поведение (UB)

// new — память + конструктор
Person* p2 = new Person("Bob"); // Конструктор вызван
p2->greet(); // OK

// Освобождение
free(p1); // Деструктор Person НЕ вызывается! Утечка ресурсов (например, std::string)
delete p2; // Деструктор вызван, память освобождена

Когда malloc всё же может быть оправдан?

Есть три легитимных сценария использования malloc в C++:

  1. Взаимодействие с C-библиотеками. Если вы передаете данные в C-функцию, которая ожидает память, выделенную через malloc (например, realloc), и сама её освобождает через free.
  2. Реализация собственных аллокаторов. При написании низкоуровневых структур данных (пулы памяти, аллокаторы для игр) malloc может быть базовым строительным блоком.
  3. Выделение памяти для POD-типов (Plain Old Data) без конструкторов. Например, массив int или char — хотя и тут лучше использовать new[]/delete[] или std::vector.

Правильный подход в современном C++

// Вместо malloc:
// int* arr = (int*)malloc(100 * sizeof(int));
// Лучше:
int* arr = new int[100]; // или std::vector<int> vec(100);

// Вместо new для одиночного объекта:
// Person* p = new Person("Alice");
// Лучше:
std::unique_ptr<Person> p = std::make_unique<Person>("Alice");
// или std::shared_ptr, если нужно разделение владения

// Для работы с C-библиотеками:
extern "C" void c_library_func(void* data);
void* buffer = malloc(1024);
c_library_func(buffer);
free(buffer); // Освобождает C-функция или мы сами

Вывод

В 99% случаев в C++ используйте new (или, лучше, умные указатели и контейнеры). malloc оставьте только для ситуаций, когда вы вынуждены работать с C-кодом или пишете кастомный аллокатор. Помните: смешивать malloc/free с new/delete для одного и того же блока памяти — грубая ошибка, приводящая к неопределенному поведению.

<<>>

Дополнение: placement new и реаллокация

Есть ещё один продвинутый сценарий, где malloc может быть полезен в паре с placement new. Это когда вы выделяете большой буфер через malloc (или получаете его от C-библиотеки), а затем создаёте объекты вручную в определённых позициях этого буфера:

#include <new> // для placement new

void* buffer = malloc(sizeof(Person) * 10);
// Создаём объект в первой ячейке буфера
Person* p = new (buffer) Person("Charlie");
// ... используем p ...
// Явно вызываем деструктор
p->~Person();
// Освобождаем сырую память
free(buffer);

Этот подход используется в:

  • Игровых движках — для пулов объектов (object pooling).
  • Встраиваемых системах — где нет исключений и нужно жёстко контролировать память.
  • Реализации контейнеров — например, std::vector внутри использует подобный механизм (выделяет сырую память, а потом создаёт элементы через placement new).

Однако это уже уровень экспертного программирования. Для повседневных задач вам достаточно правила: забудьте про malloc, используйте RAII-обёртки.