57 Stimmen

Unterscheidet sich die dynamische Speicherzuweisung in C und C++ in gängigen Implementierungen?

Was die jeweiligen Sprachstandards betrifft, so bietet C dynamische Speicherzuweisung nur durch die malloc() Familie, während in C++ die häufigste Form der Zuweisung durch ::operator new() . Das C-ähnliche malloc ist auch in C++ verfügbar, und viele "Baby's first allocator"-Beispiele verwenden es als Kern-Allokationsfunktion, aber ich bin neugierig, wie zeitgenössische Compiler den eigentlichen Produktionsoperator-new implementieren.

Ist es nur eine dünne Hülle um malloc() oder ist es aufgrund des etwas anderen Speicherzuweisungsverhaltens eines typischen C++-Programms im Vergleich zu einem typischen C-Programm grundlegend anders implementiert?

[ Edit : Ich glaube, der Hauptunterschied wird gewöhnlich wie folgt beschrieben: Ein C-Programm hat weniger, größere, langlebige Zuweisungen, während ein C++-Programm viele, kleine, kurzlebige Zuweisungen hat. Sie können sich gerne melden, wenn das falsch ist, aber es klingt, als würde man davon profitieren, dies zu berücksichtigen].

Für einen Compiler wie GCC wäre es einfach, nur eine einzige Kernzuweisungsimplementierung zu haben und diese für alle relevanten Sprachen zu verwenden, daher frage ich mich, ob es Unterschiede in den Details gibt, die versuchen, die resultierende Zuweisungsleistung in jeder Sprache zu optimieren.


Aktualisierung: Vielen Dank für die vielen tollen Antworten! Es sieht so aus, als ob in GCC dies vollständig gelöst ist durch ptmalloc und dass MSVC auch malloc im Kern. Weiß jemand, wie die MSVC-Malloc implementiert ist?

48voto

NPE Punkte 462670

Hier ist die Implementierung, die von 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;
}

Dies findet sich in libstdc++-v3/libsupc++/new_op.cc innerhalb der G++-Quelldistribution.

Wie Sie sehen können, ist es eine ziemlich dünne Hülle um malloc .

bearbeiten Bei vielen Systemen ist es möglich, eine Feinabstimmung des Verhaltens von malloc in der Regel durch den Aufruf mallopt oder das Setzen von Umgebungsvariablen. Hier ist eine Artikel Erörterung einiger unter Linux verfügbarer Funktionen.

Laut Wikipedia , glibc Versionen 2.3+ verwenden eine modifizierte Version des Allokators namens ptmalloc , die ihrerseits eine Ableitung ist von dlmalloc entworfen von Doug Lea . Interessant ist, dass in einer Artikel über dlmalloc Doug Lea vertritt die folgende Auffassung (Hervorhebung von mir):

Ich habe die erste Version des Allokators geschrieben, nachdem ich einige C++ Programme geschrieben hatte, die fast ausschließlich auf die Zuweisung von dynamischem Speicher angewiesen waren. Ich stellte fest, dass sie viel langsamer liefen und/oder viel mehr Speicherverbrauch liefen, als ich es erwartet hatte. Dies war zurückzuführen auf Eigenschaften der Speicherallokatoren auf den Systemen, auf denen ich arbeitete (hauptsächlich die damals aktuellen Versionen von SunOs und BSD). Um dem entgegenzuwirken Um dem entgegenzuwirken, schrieb ich zunächst eine Reihe von Spezial-Allokatoren in C++, normalerweise durch Überladen des Operators new für verschiedene Klassen. Einige dieser sind in einem Papier über C++-Zuweisungstechniken beschrieben, das 1989 in den C++ Report Artikel Some storage allocation Techniken für Container-Klassen.

Allerdings wurde mir bald klar, dass der Bau eines speziellen neue Klasse, die dazu neigte, dynamisch zu sein keine gute Strategie war, wenn es um die Erstellung von Klassen für die Allzweckprogrammierung Unterstützungsklassen, die ich zu dieser Zeit schrieb. (Von 1986 bis 1991 war ich der Hauptautor von libg++ , der GNU C++ Bibliothek.) Eine umfassendere Lösung wurde benötigt - das Schreiben einen Zuweiser, der gut genug war unter normalen C++- und C-Lasten damit die Programmierer spezielle Allokatoren zu schreiben, außer unter ganz besonderen Bedingungen Bedingungen.

Dieser Artikel enthält eine Beschreibung einiger der Algorithmen und Implementierungsüberlegungen für diesen Allokator.

15voto

sharptooth Punkte 162790

In den meisten Implementierungen operator new() einfache Anrufe malloc() . Tatsächlich hat sogar der Standard schlägt vor, dass als Standard-Stratege . Natürlich können Sie Ihre eigenen operator new , normalerweise für eine Klasse, wenn Sie eine bessere Leistung wünschen, aber der Standard ist normalerweise nur der Aufruf von malloc() .

13voto

cyco130 Punkte 4254

Glibc new operator ist eine dünne Hülle um malloc. Und glibc malloc verwendet unterschiedliche Strategien für unterschiedliche Größenanforderungen. Sie können die Implementierung sehen, oder zumindest die Kommentare ici .

Hier ist ein Auszug aus den Kommentaren in 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.
*/

10voto

In silico Punkte 49539

Unter Visual C++ wird das Betreten eines new Ausdruck führt mich zu diesem Ausschnitt in 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);
        }

Daher ist VC++'s new wickelt auch die malloc() anrufen.

2voto

Emilio Garavaglia Punkte 19399

Es ist keine Frage der Leistung: pA = new A hat eine andere Nebenwirkung als pA = (A*)malloc(sizeof(A));

Im zweiten Fall wird der Konstruktor von A nicht aufgerufen. Um den gleichen Effekt zu erzielen, sollten Sie Folgendes tun

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

wobei neu die "Platzierung neu" ist...

void* operator new(size_t sz, void* place) 
{ return place; }

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X