Как работают "malloc" и "new"? Чем они отличаются (в реализации)?

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

См. В чем разница между new/delete и malloc/free?

5 ответов

Решение

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

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

  1. Код содержит новое выражение, предоставляющее идентификатор типа.
  2. Компилятор выяснит, не перегружает ли тип оператор new функцией распределения.
  3. Если он обнаруживает перегрузку функции нового оператора выделения, она вызывается с использованием аргументов, заданных для 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);
  1. если оператору new не удается выделить память, он может вызвать new_handlerи надеюсь, что это имеет место. Если там все еще не хватает места, новый должен бросить std::bad_alloc или получены из него. Распределитель, который имеет throw() (гарантия без броска), в этом случае он должен вернуть нулевой указатель.
  2. Среда выполнения C++ создаст объект типа, заданного идентификатором типа, в памяти, возвращаемой функцией выделения.

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

  • 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, которая принимает дополнительные параметры, такие как

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-expression и массивы

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

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),

Какие new по-разному образуют malloc является следующим:

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

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

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

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

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

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

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

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

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

Другие вопросы по тегам