Распределено ли динамическое распределение памяти в C и С++ в популярных реализациях?
Что касается соответствующих языковых стандартов, C предлагает динамическое распределение памяти только через семейство malloc()
, а в С++ наиболее распространенная форма распределения выполняется с помощью ::operator new()
. C-style malloc также доступен на С++, и многие примеры "первого первого распределителя" используют его как функцию распределения основного ядра, но мне любопытно, как современные компиляторы реализуют фактический оператор производства - новый.
Это просто тонкая оболочка вокруг malloc()
, или она реализована принципиально по-другому из-за довольно различного поведения распределения памяти типичной программы на С++ по сравнению с обычной программой на C?
[Edit: Я считаю, что основное различие обычно описывается следующим образом: программа C имеет меньше, больше, долгоживущих ассигнований, тогда как программа на С++ имеет множество небольших, недолгосрочных распределений. Не стесняйтесь звонить, если это ошибочно, но похоже, что это было бы полезно, если учесть это.]
Для компилятора, такого как GCC, было бы просто просто реализовать одну единую реализацию распределения ядра и использовать ее для всех соответствующих языков, поэтому я задаюсь вопросом, существуют ли различия в деталях, которые пытаются оптимизировать полученную производительность распределения на каждом языке.
Обновление: Спасибо за отличные ответы! Похоже, что в GCC это полностью разрешено ptmalloc, и что MSVC также использует malloc
в ядре. Кто-нибудь знает, как реализуется MSVC-malloc?
Ответы
Ответ 1
Вот реализация, используемая g++ 4.6.1
:
_GLIBCXX_WEAK_DEFINITION void *
operator new (std::size_t sz) throw (std::bad_alloc)
{
void *p;
/* malloc (0) is unpredictable; avoid it. */
if (sz == 0)
sz = 1;
p = (void *) malloc (sz);
while (p == 0)
{
new_handler handler = __new_handler;
if (! handler)
#ifdef __EXCEPTIONS
throw bad_alloc();
#else
std::abort();
#endif
handler ();
p = (void *) malloc (sz);
}
return p;
}
Это находится в libstdc++-v3/libsupc++/new_op.cc
внутри дистрибутива источника g++.
Как вы можете видеть, это довольно тонкая обертка вокруг malloc
.
edit Во многих системах можно точно настроить поведение malloc
, как правило, путем вызова mallopt
или установки переменных среды. Вот статья , в которой обсуждаются некоторые функции, доступные в Linux.
Согласно Википедии, glibc
версии 2.3+ использует модифицированную версию распределителя называется ptmalloc
, которая сама является производной от dlmalloc
, разработанной Doug Lea. Интересно, что в статья о dlmalloc
Doug Lea дает следующий перспективу (курсив мой):
Я написал первую версию распределителя после написания некоторых С++ которые почти исключительно полагались на распределение динамической памяти. Я обнаружил, что они работают намного медленнее и/или с гораздо большим количеством как я ожидал. Это было вызвано характеристики распределителей памяти в системах, в которых я работал (в основном тогдашние версии SunOs и BSD). Чтобы противостоять это, сначала я написал ряд специализированных распределителей в С++, обычно путем перегрузки оператора new для разных классов. Некоторые из они описаны в документе о методах выделения C++, который был адаптирован к статье отчета С++ 1989 года. методы для классов контейнеров.
Однако вскоре я понял, что для каждого специального распределителя новый класс, который, как правило, был динамически распределен и широко использовался, был не хорошая стратегия при создании видов программирования общего назначения классы поддержки, которые я писал в то время. (С 1986 по 1991 год я был основного автора библиотеки libg++, библиотеки GNU С++.) Более широкий было необходимо решение - написать распределитель, который был достаточно хорош при нормальных С++ и C загружает, чтобы программисты не искушались писать специализированные распределители, за исключением особо специальных условия.
В этой статье представлено описание некоторых основных целей дизайна, алгоритмов и соображений реализации этого распределителя.
Ответ 2
В большинстве реализаций operator new()
просто вызывает malloc()
. На самом деле даже The Standard предполагает, что в качестве стратегии по умолчанию. Конечно, вы можете реализовать свой собственный operator new
, как правило, для класса, если вам нужна более высокая производительность, но по умолчанию обычно вызывается malloc()
.
Ответ 3
Новый оператор glibc - это тонкая оболочка вокруг malloc. И glibc malloc использует разные стратегии для разных запросов на распределение размера. Вы можете увидеть реализацию или, по крайней мере, комментарии здесь.
Здесь выдержка из комментариев в malloc.c:
/*
47 This is not the fastest, most space-conserving, most portable, or
48 most tunable malloc ever written. However it is among the fastest
49 while also being among the most space-conserving, portable and tunable.
50 Consistent balance across these factors results in a good general-purpose
51 allocator for malloc-intensive programs.
52
53 The main properties of the algorithms are:
54 * For large (>= 512 bytes) requests, it is a pure best-fit allocator,
55 with ties normally decided via FIFO (i.e. least recently used).
56 * For small (<= 64 bytes by default) requests, it is a caching
57 allocator, that maintains pools of quickly recycled chunks.
58 * In between, and for combinations of large and small requests, it does
59 the best it can trying to meet both goals at once.
60 * For very large requests (>= 128KB by default), it relies on system
61 memory mapping facilities, if supported.
*/
Ответ 4
В Visual С++ переход в выражение new
приводит меня к этому фрагменту в new.cpp
:
#include <cstdlib>
#include <new>
_C_LIB_DECL
int __cdecl _callnewh(size_t size) _THROW1(_STD bad_alloc);
_END_C_LIB_DECL
void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{ // try to allocate size bytes
void *p;
while ((p = malloc(size)) == 0)
if (_callnewh(size) == 0)
{ // report no memory
static const std::bad_alloc nomem;
_RAISE(nomem);
}
return (p);
}
Итак, VС++ new
также завершает вызов malloc()
.
Ответ 5
Это не вопрос производительности:
pA = new A
имеет другой побочный эффект, чем pA = (A*)malloc(sizeof(A));
Во втором, конструктор A не вызывается.
Чтобы достичь такого же эффекта, вы должны сделать
pA = (A*)malloc(sizeof(A));
new(pA)A();
где new - это "размещение нового"...
void* operator new(size_t sz, void* place)
{ return place; }