Различается ли динамическое распределение памяти в C и C ++ в популярных реализациях? - PullRequest
56 голосов
/ 16 сентября 2011

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

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

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

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


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

Ответы [ 5 ]

48 голосов
/ 16 сентября 2011

Вот реализация, используемая 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 Даг Ли дает следующую точку зрения (выделено мое):

Я написал первую версию распределителя после написания некоторых программ на C ++, которые почтиисключительно полагался на выделение динамической памяти.Я обнаружил, что они работают намного медленнее и / или с гораздо большим потреблением памяти, чем я ожидал.Это было связано с характеристиками распределителей памяти в системах, на которых я работал (главным образом, в текущих версиях SunO и BSD).Чтобы противостоять этому, сначала я написал несколько специальных распределителей на C ++, обычно перегружая оператор new для различных классов.Некоторые из них описаны в статье о методах выделения C ++, которая была адаптирована к статье отчета C ++ за 1989 г. Некоторые методы выделения хранилища для контейнерных классов.

Однако вскоре я понял, что создание специального распределителя для каждого нового классаСклонность к динамическому распределению и интенсивному использованию не была хорошей стратегией при создании видов классов поддержки программирования общего назначения, которые я писал в то время.(С 1986 по 1991 год я был основным автором libg ++, библиотеки GNU C ++.) Требовалось более широкое решение - написать распределитель, который был бы достаточно хорош при нормальных C ++, и C загружает , чтобыу программистов не возникнет соблазна написать специальные распределители, кроме как в очень особых условиях.

В этой статье представлено описание некоторых основных целей разработки, алгоритмов и соображений реализации этого распределителя.

15 голосов
/ 16 сентября 2011

В большинстве реализаций operator new() просто вызывает malloc(). На самом деле даже Стандарт предполагает, что в качестве стратегии по умолчанию . Конечно, вы можете реализовать свой собственный operator new, обычно для класса, если вы хотите повысить производительность, но по умолчанию обычно просто вызывается malloc().

12 голосов
/ 16 сентября 2011

Новый оператор 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.
*/
10 голосов
/ 16 сентября 2011

В Visual C ++ переход к выражению 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);
        }

Так что new в VC ++ также включает malloc().

2 голосов
/ 16 сентября 2011

Это не вопрос производительности: pA = new A имеет побочный эффект, отличный от pA = (A*)malloc(sizeof(A));

Во втором случае конструктор А не вызывается. Чтобы прийти к такому же эффекту, вы должны сделать

pA = (A*)malloc(sizeof(A));
new(pA)A();

где new - это "размещение нового" ...

void* operator new(size_t sz, void* place) 
{ return place; }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...