Подтвердить что ты не робот

Распределено ли динамическое распределение памяти в C и С++ в популярных реализациях?

Что касается соответствующих языковых стандартов, C предлагает динамическое распределение памяти только через семейство malloc(), а в С++ наиболее распространенная форма распределения выполняется с помощью ::operator new(). C-style malloc также доступен на С++, и многие примеры "первого первого распределителя" используют его как функцию распределения основного ядра, но мне любопытно, как современные компиляторы реализуют фактический оператор производства - новый.

Это просто тонкая оболочка вокруг malloc(), или она реализована принципиально по-другому из-за довольно различного поведения распределения памяти типичной программы на С++ по сравнению с обычной программой на C?

[Edit: Я считаю, что основное различие обычно описывается следующим образом: программа C имеет меньше, больше, долгоживущих ассигнований, тогда как программа на С++ имеет множество небольших, недолгосрочных распределений. Не стесняйтесь звонить, если это ошибочно, но похоже, что это было бы полезно, если учесть это.]

Для компилятора, такого как GCC, было бы просто просто реализовать одну единую реализацию распределения ядра и использовать ее для всех соответствующих языков, поэтому я задаюсь вопросом, существуют ли различия в деталях, которые пытаются оптимизировать полученную производительность распределения на каждом языке.


Обновление: Спасибо за отличные ответы! Похоже, что в GCC это полностью разрешено ptmalloc, и что MSVC также использует malloc в ядре. Кто-нибудь знает, как реализуется MSVC-malloc?

4b9b3361

Ответ 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; }