В каких случаях использовать malloc, а в каких new в C++?
Основное правило: используйте 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++:
- Взаимодействие с C-библиотеками. Если вы передаете данные в C-функцию, которая ожидает память, выделенную через
malloc(например,realloc), и сама её освобождает черезfree. - Реализация собственных аллокаторов. При написании низкоуровневых структур данных (пулы памяти, аллокаторы для игр)
mallocможет быть базовым строительным блоком. - Выделение памяти для 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-обёртки.