Как работают "malloc" и "new"? Как они различны (мудрость реализации)?

Я знаю, как они отличаются синтаксически, и что С++ использует новые, а C использует malloc. Но как они работают, в объяснении высокого уровня?

См. В чем разница между новым/удаленным и malloc/бесплатным?

Ответы

Ответ 1

Я просто собираюсь направить вас на этот ответ: В чем разница между новым/удаленным и malloc/бесплатным?. Мартин предоставил отличный обзор. Быстрый обзор того, как они работают (без погружения в то, как вы можете перегрузить их как функции-члены):

новое выражение и выделение

  • Код содержит новое выражение, предоставляющее идентификатор типа.
  • Компилятор рассмотрит, перегружает ли оператор новый с помощью функции распределения.
  • Если он обнаруживает перегрузку новой функции распределения оператора, ее вызывается с использованием аргументов, данных для new и sizeof (TypeId) в качестве первого аргумента:

Пример:

new (a, b, c) TypeId;

// the function called by the compiler has to have the following signature:
operator new(std::size_t size, TypeOfA a, TypeOfB b, TypeOf C c);
  • Если оператор new не может выделить хранилище, он может вызвать new_handler и надеяться, что это произойдет. Если места по-прежнему недостаточно, новый должен выбросить std::bad_alloc или получить от него. Распределитель, который имеет throw() (гарантия отсутствия броска), в этом случае возвращает нулевой указатель.
  • Среда выполнения С++ создаст объект типа, заданный идентификатором типа в памяти, возвращаемой функцией распределения.

Есть несколько специальных функций выделения, заданных специальными именами:

  • no-throw новый. В качестве второго аргумента требуется nothrow_t. Новое выражение формы следующего вида вызовет функцию распределения, принимающую только std:: size_t и nothrow_t:

Пример:

new (std::nothrow) TypeId;
  • placement new. Это принимает указатель void * как первый аргумент, и вместо того, чтобы возвращать вновь выделенный адрес памяти, он возвращает этот аргумент. Он используется для создания объекта по заданному адресу. Стандартные контейнеры используют это для предопределения пространства, но только при необходимости создают объекты.

код:

// the following function is defined implicitly in the standard library
void * operator(std::size_t size, void * ptr) throw() {
    return ptr;
}

Если функция распределения возвращает хранилище, а конструктор объекта, созданного броском выполнения, то оператор delete вызывается автоматически. В случае использования новой формы, которая принимает дополнительные параметры, такие как

new (a, b, c) TypeId;

Затем вызывается оператор delete, который принимает эти параметры. Эта версия удаления оператора вызывается только в том случае, если удаление завершено, потому что конструктор объекта бросил. Если вы вызываете delete самостоятельно, тогда компилятор будет использовать функцию нормального удаления оператора, используя только указатель void*:

int * a = new int;
=> void * operator new(std::size_t size) throw(std::bad_alloc);
delete a;
=> void operator delete(void * ptr) throw();

TypeWhosCtorThrows * a = new ("argument") TypeWhosCtorThrows;
=> void * operator new(std::size_t size, char const* arg1) throw(std::bad_alloc);
=> void operator delete(void * ptr, char const* arg1) throw();

TypeWhosCtorDoesntThrow * a = new ("argument") TypeWhosCtorDoesntThrow;
=> void * operator new(std::size_t size, char const* arg1) throw(std::bad_alloc);
delete a;
=> void operator delete(void * ptr) throw();

новое выражение и массивы

Если вы делаете

new (possible_arguments) TypeId[N];

Компилятор использует функции operator new[] вместо обычного operator new. Оператору может быть передан первый аргумент не точно sizeof(TypeId)*N: компилятор мог бы добавить некоторое пространство для хранения количества созданных объектов (необязательно для вызова деструкторов). Стандарт ставит это так:

  • new T[5] приводит к вызову оператора new[](sizeof(T)*5+x) и
  • new(2,f) T[5] приводит к вызову оператора new[](sizeof(T)*5+y,2,f).

Ответ 2

То, что new делает иначе malloc, выглядит следующим образом:

  • Он создает значение в выделенной памяти, вызывая operator new. Такое поведение можно адаптировать, перегружая этот оператор либо для всех типов, либо только для вашего класса.
  • Он вызывает функции обработчика, если память не может быть выделена. Это дает вам возможность бесплатно освободить требуемую память на лету, если вы заранее зарегистрировали такую ​​функцию обработчика.
  • Если это не помогает (например, потому что вы не регистрировали какую-либо функцию), оно генерирует исключение.

Таким образом, в целом new настраивается, а также выполняет инициализацию, помимо распределения памяти. Это две большие различия.

Ответ 3

Хотя malloc/free и new/delete имеют разные типы поведения, оба они делают то же самое на низком уровне: управляют динамически распределенной памятью. Я предполагаю, что это то, о чем вы действительно спрашиваете. В моей системе new на самом деле вызывает malloc внутренне для выполнения его выделения, поэтому я просто расскажу о malloc.

Фактическая реализация malloc и free может сильно различаться, поскольку существует множество способов реализации выделения памяти. Некоторые подходы улучшают производительность, некоторые теряют память, другие лучше подходят для отладки. Собранные мусором языки могут также иметь совершенно разные способы выделения, но ваш вопрос касался C/С++.

В общем случае блоки выделяются из кучи, большой области памяти в адресном пространстве программы. Библиотека управляет кучей для вас, обычно используя системные вызовы, такие как sbrk или mmap. Один подход к распределению блоков из кучи состоит в том, чтобы поддерживать список свободных и выделенных блоков, в которых хранятся размеры блоков и местоположения. Первоначально список может содержать один большой блок для всей кучи. Когда запрашивается новый блок, распределитель выберет свободный блок из списка. Если блок слишком велик, его можно разделить на два блока (один из запрошенных размеров, другой - любой размер). Когда выделенный блок освобождается, его можно объединить с смежными свободными блоками, поскольку наличие одного большого свободного блока более полезно, чем несколько небольших свободных блоков. Фактический список блоков может быть сохранен как отдельные структуры данных или встроен в кучу.

Существует много вариантов. Возможно, вам захочется сохранить отдельные списки свободных и выделенных блоков. Вы можете получить более высокую производительность, если у вас есть отдельные области кучи для блоков общих размеров или отдельных списков для этих размеров. Например, когда вы выделили 16-байтовый блок, распределитель может иметь специальный список из 16-байтовых блоков, поэтому выделение может быть O (1). Также может быть полезно иметь дело только с размерами блоков, которые имеют степень 2 (все остальное округляется). Например, Buddy allocator работает таким образом.

Ответ 4

"новый" делает намного больше, чем malloc. malloc просто выделяет память - она ​​даже не равна нулю для вас. новые объекты инициализации, вызовы contructors и т.д. Я бы заподозрил, что в большинстве реализаций новый - это немного больше, чем тонкая оболочка вокруг malloc для базовых типов.

Ответ 5

В C: malloc выделяет кусок памяти размера, который вы предоставляете в аргументе, и возвращает указатель на эту память.

Память объявляется в куче, поэтому не забудьте освободить ее, когда закончите.