В каких случаях я использую malloc против new?
Я вижу, что в C++ есть несколько способов размещения и освобождения данных, и я понимаю, что когда вы вызываете malloc
ты должен позвонить free
и когда вы используете new
оператор, с которым вы должны соединиться delete
и это ошибка смешивать два (например, free()
на то, что было создано с new
оператор), но я не знаю, когда мне следует использовать malloc
/ free
и когда я должен использовать new
/ delete
в моих реальных программах мира.
Если вы эксперт по C++, пожалуйста, дайте мне знать практические правила или соглашения, которые вы соблюдаете в этом отношении.
21 ответ
Если вы не вынуждены использовать C, вы никогда не должны использовать malloc
, Всегда используйте new
,
Если вам нужен большой кусок данных, просто сделайте что-то вроде:
char *pBuffer = new char[1024];
Будьте осторожны, хотя это не правильно:
//This is incorrect - may delete only one element, may corrupt the heap, or worse...
delete pBuffer;
Вместо этого вы должны сделать это при удалении массива данных:
//This deletes all items in the array
delete[] pBuffer;
new
Ключевое слово - это способ C++, и он гарантирует, что ваш тип будет иметь свой конструктор. new
ключевое слово также является более безопасным, тогда как malloc
не является типобезопасным вообще.
Единственный способ, которым я мог подумать, что было бы полезно использовать malloc
будет, если вам нужно изменить размер буфера данных. new
Ключевое слово не имеет аналогичного способа, как realloc
, realloc
Функция может увеличить размер фрагмента памяти для вас более эффективно.
Стоит отметить, что вы не можете смешивать new
/ free
а также malloc
/ delete
,
Примечание. Некоторые ответы на этот вопрос неверны.
int* p_scalar = new int(5); // Does not create 5 elements, but initializes to 5
int* p_array = new int[5]; // Creates 5 elements
Краткий ответ: не используйте malloc
для C++ без действительно веской причины для этого. malloc
имеет ряд недостатков при использовании с C++, который new
был определен, чтобы преодолеть.
Недостатки, исправленные новым кодом C++
malloc
не является безопасным в любом случае. В C++ вам необходимо привести результат изvoid*
, Это потенциально создает много проблем:#include <stdlib.h> struct foo { double d[5]; }; int main() { foo *f1 = malloc(1); // error, no cast foo *f2 = static_cast<foo*>(malloc(sizeof(foo))); foo *f3 = static_cast<foo*>(malloc(1)); // No error, bad }
Хотя это еще хуже. Если речь идет о POD (простые старые данные), тогда вы можете использовать
malloc
выделить память для него, какf2
делает в первом примере.Это не так очевидно, если тип является POD. Важным фактором является тот факт, что данный тип может измениться с POD на не POD без какой-либо результирующей ошибки компилятора и потенциально очень трудно отладить проблемы. Например, если кто-то (возможно, другой программист, во время обслуживания, намного позже должен был внести изменение, которое вызвало
foo
Если POD больше не будет POD, то во время компиляции не возникнет явной ошибки, как вы надеетесь, например:struct foo { double d[5]; virtual ~foo() { } };
сделает
malloc
изf2
тоже стало плохо, без какой-либо явной диагностики. Пример здесь тривиален, но можно случайно ввести non-PODness гораздо дальше (например, в базовый класс, добавив не POD-член). Если у вас есть C++11/boost, вы можете использоватьis_pod
проверить правильность этого предположения и выдать ошибку, если это не так:#include <type_traits> #include <stdlib.h> foo *safe_foo_malloc() { static_assert(std::is_pod<foo>::value, "foo must be POD"); return static_cast<foo*>(malloc(sizeof(foo))); }
Хотя boost не может определить, является ли тип POD без C++ 11 или каких-либо других расширений компилятора.
malloc
возвращаетсяNULL
если распределение не удается.new
будет бросатьstd::bad_alloc
, Поведение позже с помощьюNULL
указатель не определен Исключение имеет чистую семантику, когда оно генерируется и выбрасывается из источника ошибки. Упаковкаmalloc
с соответствующим тестом при каждом вызове кажется утомительным и подверженным ошибкам. (Вы должны только забыть один раз, чтобы отменить всю эту хорошую работу). Исключению может быть разрешено распространяться до уровня, когда вызывающая сторона способна его разумно обработать, где, какNULL
гораздо сложнее передать обратно по смыслу. Мы могли бы расширить нашsafe_foo_malloc
функция, чтобы вызвать исключение или выйти из программы или вызвать некоторый обработчик:#include <type_traits> #include <stdlib.h> void my_malloc_failed_handler(); foo *safe_foo_malloc() { static_assert(std::is_pod<foo>::value, "foo must be POD"); foo *mem = static_cast<foo*>(malloc(sizeof(foo))); if (!mem) { my_malloc_failed_handler(); // or throw ... } return mem; }
В корне
malloc
это особенность C иnew
это функция C++. В следствииmalloc
не очень хорошо работает с конструкторами, он только смотрит на выделение части байтов. Мы могли бы расширить нашsafe_foo_malloc
дальше использовать размещениеnew
:#include <stdlib.h> #include <new> void my_malloc_failed_handler(); foo *safe_foo_malloc() { void *mem = malloc(sizeof(foo)); if (!mem) { my_malloc_failed_handler(); // or throw ... } return new (mem)foo(); }
наш
safe_foo_malloc
функция не очень общая - в идеале нам нужно что-то, что может обрабатывать любой тип, а не толькоfoo
, Мы можем достичь этого с помощью шаблонов и шаблонов с переменными параметрами для конструкторов, отличных от заданных по умолчанию:#include <functional> #include <new> #include <stdlib.h> void my_malloc_failed_handler(); template <typename T> struct alloc { template <typename ...Args> static T *safe_malloc(Args&&... args) { void *mem = malloc(sizeof(T)); if (!mem) { my_malloc_failed_handler(); // or throw ... } return new (mem)T(std::forward(args)...); } };
Теперь, исправляя все выявленные нами проблемы, мы практически заново изобрели стандартную настройку.
new
оператор. Если вы собираетесь использоватьmalloc
и размещениеnew
тогда вы могли бы просто использоватьnew
начать с!
Из C++ FQA Lite:
[16.4] Почему я должен использовать новый вместо надежного старого malloc ()?
FAQ: новый / удалить вызов конструктора / деструктора; new - это безопасный тип, malloc - нет; new может быть переопределено классом.
FQA: достоинства нового, упомянутые в FAQ, не являются достоинствами, потому что конструкторы, деструкторы и перегрузка операторов являются мусором (посмотрите, что происходит, когда у вас нет сборки мусора?), И проблема безопасности типов здесь очень мала (обычно у вас есть привести void*, возвращаемый malloc, к правому типу указателя, чтобы присвоить его типизированной переменной-указателю, что может раздражать, но далеко не "небезопасно").
О, и использование заслуживающего доверия старого malloc делает возможным использование такого же заслуживающего доверия и старого realloc. Жаль, что у нас нет нового оператора или что-то в этом роде.
Тем не менее, new не является достаточно плохим, чтобы оправдать отклонение от общего стиля, используемого во всем языке, даже когда язык - C++. В частности, классы с нетривиальными конструкторами будут вести себя фатально, если вы просто неправильно размещаете объекты. Так почему бы не использовать новое по всему коду? Люди редко перегружают оператора новым, так что, вероятно, он не будет слишком мешать вам. И если они действительно перегружают новые, вы всегда можете попросить их остановиться.
Извините, я просто не смог устоять.:)
Всегда используйте новое в C++. Если вам нужен блок нетипизированной памяти, вы можете напрямую использовать оператор new:
void *p = operator new(size);
...
operator delete(p);
новый против malloc ()
1) new
является оператором, а malloc()
это функция.
2) new
вызывает конструкторы, а malloc()
не.
3) new
возвращает точный тип данных, в то время как malloc()
возвращает недействительным *.
4) new
никогда не возвращает NULL (будет сбрасывать при неудаче), пока malloc()
возвращает NULL
5) Перераспределение памяти не обрабатывается new
в то время как malloc()
Можно
Чтобы ответить на ваш вопрос, вы должны знать разницу между malloc
а также new
, Разница проста:
malloc
выделяет память, в то время как new
Выделяет память И вызывает конструктор объекта, для которого вы выделяете память.
Поэтому, если вы не ограничены C, вы никогда не должны использовать malloc, особенно когда имеете дело с объектами C++. Это был бы рецепт взлома вашей программы.
Также разница между free
а также delete
совсем то же самое. Разница в том, что delete
вызовет деструктор вашего объекта в дополнение к освобождению памяти.
Использование malloc
а также free
только для выделения памяти, которая будет управляться с-центрическими библиотеками и API. использование new
а также delete
(и []
варианты) для всего, что вы контролируете.
Есть одна большая разница между malloc
а также new
, malloc
выделяет память. Это хорошо для C, потому что в C кусок памяти является объектом.
В C++, если вы не имеете дело с типами POD (которые похожи на типы C), вы должны вызвать конструктор в ячейке памяти, чтобы фактически иметь там объект. Не-POD типы очень распространены в C++, так как многие функции C++ делают объект автоматически не POD.
new
выделяет память и создает объект в этой области памяти. Для не POD-типов это означает вызов конструктора.
Если вы делаете что-то вроде этого:
non_pod_type* p = (non_pod_type*) malloc(sizeof *p);
Полученный вами указатель не может быть разыменован, поскольку он не указывает на объект. Вам нужно вызвать конструктор, прежде чем вы сможете его использовать (и это делается с помощью размещения new
).
Если, с другой стороны, вы делаете:
non_pod_type* p = new non_pod_type();
Вы получаете указатель, который всегда действителен, потому что new
создал объект.
Даже для типов POD между ними есть существенная разница:
pod_type* p = (pod_type*) malloc(sizeof *p);
std::cout << p->foo;
Этот фрагмент кода напечатает неопределенное значение, потому что объекты POD, созданные malloc
не инициализируются.
С new
Вы можете указать конструктор для вызова и, таким образом, получить четко определенное значение.
pod_type* p = new pod_type();
std::cout << p->foo; // prints 0
Если вы действительно этого хотите, вы можете использовать new
получить неинициализированные объекты POD. Смотрите этот другой ответ для получения дополнительной информации об этом.
Другое отличие - поведение при неудаче. Когда не удается выделить память, malloc
возвращает нулевой указатель, а new
бросает исключение.
Первый требует, чтобы вы проверяли каждый указатель, возвращенный перед его использованием, тогда как последний всегда будет выдавать действительные указатели.
По этим причинам в коде C++ вы должны использовать new
, и не malloc
, Но даже тогда вы не должны использовать new
"под открытым небом", потому что он получает ресурсы, которые вы должны выпустить позже. Когда вы используете new
Вы должны немедленно передать его результат в класс управления ресурсами:
std::unique_ptr<T> p = std::unique_ptr<T>(new T()); // this won't leak
Динамическое размещение требуется только в том случае, если время жизни объекта должно отличаться от области, в которой он создается (это также относится к уменьшению и увеличению области), и у вас есть особая причина, по которой его сохранение по значению не Работа.
Например:
std::vector<int> *createVector(); // Bad
std::vector<int> createVector(); // Good
auto v = new std::vector<int>(); // Bad
auto result = calculate(/*optional output = */ v);
auto v = std::vector<int>(); // Good
auto result = calculate(/*optional output = */ &v);
Начиная с C++11 мы имеем std::unique_ptr
для работы с выделенной памятью, которая содержит владение выделенной памятью. std::shared_ptr
был создан для того, когда вы должны делиться собственностью. (вам нужно это меньше, чем вы ожидаете в хорошей программе)
Создание экземпляра становится действительно простым:
auto instance = std::make_unique<Class>(/*args*/); // C++14
auto instance = std::make_unique<Class>(new Class(/*args*/)); // C++11
auto instance = std::make_unique<Class[]>(42); // C++14
auto instance = std::make_unique<Class[]>(new Class[](42)); // C++11
C++ 17 также добавляет std::optional
что может помешать вам требовать выделения памяти
auto optInstance = std::optional<Class>{};
if (condition)
optInstance = Class{};
Как только "экземпляр" выходит из области видимости, память очищается. Передача права собственности также проста:
auto vector = std::vector<std::unique_ptr<Interface>>{};
auto instance = std::make_unique<Class>();
vector.push_back(std::move(instance)); // std::move -> transfer (most of the time)
Итак, когда вам еще нужно new
? Почти никогда не начиная с C++11. Большую часть вы используете std::make_unique
пока вы не дойдете до точки, где вы нажмете API, который передает право собственности через необработанные указатели.
auto instance = std::make_unique<Class>();
legacyFunction(instance.release()); // Ownership being transferred
auto instance = std::unique_ptr<Class>{legacyFunction()}; // Ownership being captured in unique_ptr
В C++98/03 вы должны выполнять ручное управление памятью. Если вы в этом случае, попробуйте обновить до более новой версии стандарта. Если вы застряли:
auto instance = new Class(); // Allocate memory
delete instance; // Deallocate
auto instances = new Class[42](); // Allocate memory
delete[] instances; // Deallocate
Убедитесь, что вы правильно отслеживаете владение, чтобы не было утечек памяти! Семантика Move еще не работает.
Итак, когда нам нужен malloc в C++? Единственная действительная причина заключается в том, чтобы выделить память и инициализировать ее позже путем размещения нового.
auto instanceBlob = std::malloc(sizeof(Class)); // Allocate memory
auto instance = new(instanceBlob)Class{}; // Initialize via constructor
instance.~Class(); // Destroy via destructor
std::free(instanceBlob); // Deallocate the memory
Несмотря на то, что вышеупомянутое действительно, это может быть сделано через оператора new. std::vector
хороший пример для этого.
Наконец, у нас все еще есть слон в комнате: C
, Если вам приходится работать с C-библиотекой, где память выделяется в коде C++ и освобождается в коде C (или наоборот), вы вынуждены использовать malloc/free.
Если вы в этом случае, забудьте о виртуальных функциях, функциях-членах, классах... Разрешены только структуры с POD.
Некоторые исключения из правил:
- Вы пишете стандартную библиотеку с продвинутыми структурами данных, где подходит malloc
- Вы должны выделить большие объемы памяти (в оперативной копии файла 10 ГБ?)
- У вас есть инструменты, мешающие вам использовать определенные конструкции
- Вам нужно хранить неполный тип
Есть несколько вещей, которые new
делает это malloc
нет:
new
создает объект, вызывая конструктор этого объектаnew
не требует типизации выделенной памяти.- Для этого не нужно выделять объем памяти, скорее, для этого нужно несколько объектов.
Итак, если вы используете malloc
, то вам нужно делать вышеперечисленные вещи явно, что не всегда практично. Дополнительно, new
может быть перегружен, но malloc
не может быть
Если вы работаете с данными, которые не нуждаются в создании / уничтожении и требуют перераспределения (например, большого массива целых чисел), то я считаю, что malloc/free - это хороший выбор, поскольку он дает вам realloc, который намного быстрее, чем new-memcpy -delete (это на моем Linux-компьютере, но я думаю, это может зависеть от платформы). Если вы работаете с объектами C++, которые не являются POD и требуют построения / уничтожения, то вы должны использовать операторы new и delete.
Во всяком случае, я не понимаю, почему вы не должны использовать оба (при условии, что вы освобождаете свою незанятую память и удаляете объекты, выделенные с новым), если можете воспользоваться преимуществами ускорения (иногда значительного, если вы перераспределяете большие массивы). POD), который может дать вам realloc.
Если вам это не нужно, вы должны придерживаться нового / удалить в C++.
Если вы используете C++, тогда попробуйте использовать new/delete вместо malloc/calloc, поскольку они сами по себе операторы по сравнению с malloc/calloc, для которых вы использовали для этого другой заголовок. Поэтому не смешивайте два разных языка в одном коде Их работа одинакова во всех отношениях, и оба динамически распределяют память из сегмента кучи в хэш-таблице.
new
инициализирует значения по умолчанию структуры и правильно связывает ссылки в ней с собой.
Например
struct test_s {
int some_strange_name = 1;
int &easy = some_strange_name;
}
Так new struct test_s
вернет инициализированную структуру с рабочей ссылкой, в то время как версия malloc не имеет значений по умолчанию, а внутренние ссылки не инициализированы.
С более низкой точки зрения, new инициализирует всю память перед предоставлением памяти, тогда как malloc сохранит исходное содержимое памяти.
Если у вас есть C-код, который вы хотите перенести на C++, вы можете оставить в нем любые вызовы malloc(). Для любого нового кода C++ я бы рекомендовал использовать вместо него новый.
В следующем сценарии мы не можем использовать new, так как он вызывает конструктор.
class B {
private:
B *ptr;
int x;
public:
B(int n) {
cout<<"B: ctr"<<endl;
//ptr = new B; //keep calling ctr, result is segmentation fault
ptr = (B *)malloc(sizeof(B));
x = n;
ptr->x = n + 10;
}
~B() {
//delete ptr;
free(ptr);
cout<<"B: dtr"<<endl;
}
};
Редкий случай, чтобы рассмотреть возможность использования malloc/free вместо new/delete, это когда вы выделяете, а затем перераспределяете (простые типы pod, а не объекты), используя realloc, так как в C++ нет функции, аналогичной realloc (хотя это можно сделать с использованием более C++ подход)
Раньше я играл с несколькими приложениями C/C++ для компьютерной графики. Спустя столько времени некоторые вещи исчезли, и я очень скучал по ним.
Дело в том, что malloc и new или free и delete могут работать и то, и другое, особенно для определенных базовых типов, которые являются наиболее распространенными.
Например, массив символов может быть выделен как с помощью malloc, так и с помощью new. Основное отличие состоит в том, что с помощью new вы можете создать экземпляр фиксированного размера массива.
char* pWord = new char[5]; // allocation of char array of fixed size
В этом случае вы не можете использовать переменную для размера массива. Наоборот, функция malloc может допускать переменный размер.
int size = 5;
char* pWord = (char*)malloc(size);
В этом случае может потребоваться оператор приведения преобразования. Для типа, возвращаемого malloc, это указатель на void, а не на char. А иногда компилятор не мог знать, как преобразовать этот тип.
После выделения блока памяти вы можете установить значения переменных. функция memset может быть действительно медленнее для некоторых больших массивов. Но все биты должны быть сначала установлены в 0, прежде чем присваивать значение. Потому что значения массива могут иметь произвольное содержимое.
Предположим, массиву присвоен другой массив меньшего размера. Часть элемента массива может иметь произвольное содержимое. И в этом случае рекомендуется вызов функции memset.
memset((void*)pWord, 0, sizeof(pWord) / sizeof(char));
Функции распределения доступны для всех пакетов C. Итак, это общие функции, которые должны работать для большего количества типов C. А библиотеки C++ являются расширениями старых библиотек C. Поэтому функция malloc возвращает универсальный указатель void*. В структурах не определен новый оператор или оператор удаления. В этом случае пользовательская переменная может быть выделена с помощью malloc.
Ключевые слова new и delete на самом деле являются некоторыми определенными операторами C. Возможно, эти операторы были определены пользовательским объединением или классом. Если new и delete не определены в классе, они могут не работать. Но если класс является производным от другого, в котором есть эти операторы, ключевые слова new и delete могут вести себя как базовый класс.
Об освобождении массива: free можно использовать только в паре с malloc. Невозможно выделить переменную с помощью malloc, а затем освободить с помощью удаления.
Простой оператор удаления ссылается только на первый элемент массива. Поскольку массив pWord также можно записать как:
pWord = &pWord[0]; // or *pWord = pWord[0];
Когда массив необходимо удалить, используйте вместо него оператор delete[]:
delete[] pWord;
Приведения не плохи, просто они не работают для всех типов переменных. Приведение преобразования также является операторной функцией, которую необходимо определить. Если этот оператор не определен для определенного типа, он может не работать. Но не все ошибки связаны с этим оператором приведения преобразования.
Также при использовании свободного вызова необходимо использовать приведение к пустому указателю. Это связано с тем, что аргумент функции free является пустым указателем.
free((void*)pWord);
Некоторые ошибки могут возникать из-за того, что размер массива слишком мал. Но это уже другая история, это не из-за использования гипса.
С уважением, Адриан Бринас
Кстати, если у вас есть код с malloc, используйте это регулярное выражение:
Ищи:
\((\w+)\*\)malloc\((\w+)\)
Заменить:
new \1[\2]
new
а также delete
операторы могут работать с классами и структурами, тогда как malloc
а также free
Работайте только с теми блоками памяти, которые нужно привести.
С помощью new/delete
поможет улучшить ваш код, так как вам не нужно будет приводить выделенную память к необходимой структуре данных.
malloc() используется для динамического выделения памяти в C, в то время как та же самая работа выполняется new() в C++. Таким образом, вы не можете смешивать соглашения о кодировании 2 языков. Было бы хорошо, если бы вы спросили разницу между calloc и malloc()