Frage

Ich habe gesucht, aber ich habe nicht sehr gut, diese drei Begriffe zu verstehen. Wann muss ich (in der Halde) dynamische Zuordnung verwenden, und was seine wirklichen Vorteil? Was sind die Probleme der statischen und stapeln? Könnte ich ohne Zuweisung Variablen in der Halde eine gesamte Anwendung schreiben?

Ich habe gehört, dass andere Sprachen enthalten einen „Garbage Collector“ und so muss man über das Gedächtnis keine Sorge. Was macht die Garbage Collector?

Was könnten Sie selbst den Speicher Sie manipulieren, dass Sie mit dieser Garbage Collector nicht tun könnten?

Sobald jemand sagte mir, dass diese Erklärung an:

int * asafe=new int;

Ich habe einen „Zeiger auf einen Zeiger“. Was heißt das? Es unterscheidet sich von:

asafe=new int;

War es hilfreich?

Lösung

Eine ähnliche Frage gefragt wurde, aber es didn ‚t über Statik fragen.

Zusammenfassung von dem, was statisch, Heap und Stack-Speicher sind:

  • Eine statische Variable ist im Grunde eine globale Variable, auch wenn Sie es nicht global zugreifen können. Normalerweise gibt es eine Adresse für sie, die in der ausführbaren Datei selbst ist. Es gibt nur eine Kopie für das gesamte Programm. Egal, wie oft Sie einen Funktionsaufruf gehen in (oder Klasse) (und in wie viele Threads!) Die Variable bezogen auf den gleichen Speicherplatz.

  • Der Haufen ist ein Bündel von Speichern, die dynamisch verwendet werden können. Wenn Sie 4kb für ein Objekt soll dann der dynamische Zuordner durch die Liste der Freiraum in dem Haufen aussehen wird, ein 4kb Brocken herausgreifen, und geben es Ihnen. Im Allgemeinen ist die dynamische Speicherzuweisung (malloc, neu, et c.) Beginnt am Ende des Speichers und arbeitet nach hinten.

  • Zu erklären, wie ein Stapel wächst und schrumpft ein wenig außerhalb des Umfangs dieser Antwort, aber es genügt Sie immer zu sagen, hinzufügen und nur vom Ende entfernen. Stacks beginnt in der Regel hoch und wachsen zu niedrigeren Adressen. Sie führen aus dem Speicher, wenn der Stapel das dynamische allocator irgendwo in der Mitte trifft (aber zu physischen gegenüber der virtuellen Speicher und Fragmentierung beziehen). Mehrere Threads werden mehrere Stapel erfordern (das Verfahren im allgemeinen eine Mindestgröße für den Stapel behält).

Wenn Sie wollen jedes verwenden:

  • Statiken / Globals sind nützlich für die Erinnerung, die Sie wissen, dass Sie immer brauchen, und Sie wissen, dass Sie überhaupt nicht freigeben wollen. (By the way, Embedded-Umgebungen können nur statische Speicher haben gedacht werden ... der Stack und Heap ist Teil eines Adressraum bekannt durch eine dritte Speichertyp geteilt:. Programmcodes Programme oft dynamische Zuweisung tun aus ihrem statische Speicher, wenn sie Dinge wie verkettete Listen müssen. aber egal, der statische Speicher selbst (der Puffer) nicht selbst „zugeordnet“, sondern andere Aufgaben werden aus dem Speicher durch den Puffer für diesen Zweck gehalten zugewiesen. Sie können dies tun als auch in nicht-eingebettet und Konsolenspiele werden häufig die in dynamischen Speichermechanismen zugunsten eng Steuerung des Zuweisungsverfahrens gebaut eschew durch Puffern von voreingestellten Größen für alle Zuweisungen verwenden.)

  • Stack-Variablen sind nützlich, wenn Sie wissen, dass, solange die Funktion im Gültigkeitsbereich befindet (auf dem Stack irgendwo), werden Sie die Variablen bleiben wollen. Stacks sind schön für Variablen, die Sie für den Code benötigen, wo sie sich befinden, die aber nicht außerhalb dieses Code benötigt. Sie sind auch sehr schön, wenn Sie eine Ressource zugreifen, wie eine Datei, und wollen, dass die Ressource automatisch weg zu gehen, wenn Sie diesen Code zu verlassen.

  • Heapzuweisungen (dynamisch zugewiesenen Speicher) ist nützlich, wenn Sie flexibler sein wollen als die oben genannten. Häufig wird eine Funktion auf ein Ereignis zu reagieren genannt (der Benutzer die „erstellen Box“ klickt Taste). Die richtige Antwort kann es erforderlich, ein neues Objekt Zuweisung (ein neues Box-Objekt), langen dableiben soll, nachdem die Funktion beendet wird, so kann es nicht auf dem Stapel sein. Aber Sie wissen nicht, wie viele Boxen Sie beim Start des Programms wollen würden, so kann es nicht statisch sein.

Garbage Collection

ich in letzter Zeit viel darüber, wie groß Garbage Collectors ist gehört habe, vielleicht ein bisschen eine abweichende Stimme hilfreich wäre.

Garbage Collection ist ein wunderbarer Mechanismus, wenn die Leistung ist kein großes Problem. Ich höre GCs werden immer besser und anspruchsvolle, aber die Tatsache ist, können Sie gezwungen, eine Leistungseinbuße (je nach Anwendungsfall) zu übernehmen. Und wenn Sie faul sind, kann es immer noch nicht richtig funktioniert. Zu den besten Zeiten, Garbage Collectors erkennen, dass Ihr Gedächtnis geht weg, wenn es erkennt, dass es keine weiteren Hinweise darauf (siehe weist darauf hin, dass es O (n) für einigermaßen effiziente Algorithmen ist. das ist immer noch O (n) zu viel, wenn Sie mit der Leistung angeht und kann in konstanter Zeit ohne Garbage collection freigeben. )

Persönlich, wenn ich die Leute sagen hören, dass C ++ nicht Garbage Collection hat, meiner Meinung nach Tags, die als ein Merkmal von C ++, aber ich bin wahrscheinlich in der Minderheit. Wahrscheinlich das härteste, was für Menschen über die Programmierung in C und C ++ zu lernen, sind Zeiger und wie man richtig ihre dynamischen Speicherzuordnungen zu behandeln. Einige andere Sprachen wie Python, wären schrecklich ohne GC, so dass ich denke, es kommt darauf an, was man aus einer Sprache will. Wenn Sie zuverlässige Leistung wollen, dann C ++ ohne Garbage Collection ist das einzige, was auf dieser Seite des Fortran, die ich mir vorstellen kann. Wenn Sie (abstürzt Sie zu speichern, ohne dass Sie „richtigen“ Speicherverwaltung lernen) Nutzung und Trainingsräder wollen Leichtigkeit von, nehmen etwas mit einem GC. Selbst wenn Sie wissen, wie das Gedächtnis gut zu verwalten, wird es Zeit sparen, die Sie anderen Code zu optimieren verbringen können. Es gibt wirklich nicht viel von einer Leistungseinbuße mehr, aber wenn Sie wirklich zuverlässige Leistung benötigen (und die Fähigkeit, genau zu wissen, was los ist, wenn, unter der Decke), dann würde ich mit C ++ bleiben. Es gibt einen Grund dafür, dass alle großen Game-Engine, die ich je von in C ++ gehört habe (wenn nicht C oder Assembler). Python, et al ist in Ordnung für Scripting, aber nicht das Haupt Spiel-Engine.

Andere Tipps

Hier finden Sie natürlich alle nicht ganz präzise. Nehmen Sie es mit einem Körnchen Salz, wenn Sie es lesen:)

Nun, die drei Dinge, die Sie beziehen sich auf sind Automatische, statische und dynamische Speicherdauer , die etwas mit dem zu tun hat, wie lange Objekte leben und wenn sie beginnen, das Leben.


Automatische Lagerdauer

Sie verwenden automatische Speicherdauer für kurzlebig und kleine Daten, die benötigt wird, nur In der Nähe innerhalb einiger Block:

if(some condition) {
    int a[3]; // array a has automatic storage duration
    fill_it(a);
    print_it(a);
}

Die Lebensdauer endet, sobald wir den Block verlassen, und es beginnt, sobald das Objekt definiert ist. Sie sind die einfachste Art von Lagerdauer und sind viel schneller als besonders dynamische Speicherdauer.


Statische Speicherdauer

Sie verwenden statische Speicherdauer für freie Variablen, die von jedem Code jederzeit zugegriffen werden kann, wenn deren Umfang erlaubt eine solche Nutzung (Namespacebereich) und für lokale Variablen, die ihre Lebensdauer über Ausgang ihres Umfangs erstrecken müssen (lokalen Bereich ) und für die Elementvariablen, die von allen Objekten ihrer Klasse (classs scope) geteilt werden müssen. Ihre Lebensdauer hängt vom Umfang sie in sind. Sie haben Namespacebereich und lokaler Bereich und Klassenbereich . Was ist sie beide wahr ist, sobald ihr Leben beginnt, Lebensdauer endet bei Ende des Programms . Hier sind zwei Beispiele:

// static storage duration. in global namespace scope
string globalA; 
int main() {
    foo();
    foo();
}

void foo() {
    // static storage duration. in local scope
    static string localA;
    localA += "ab"
    cout << localA;
}

Das Programm druckt ababab, weil localA nicht beim Verlassen seines Blockes zerstört wird. Man kann sagen, dass Objekte, die lokalen Bereich Lebensdauer beginnen haben , wenn die Steuerung ihre Definition erreicht . Für localA kommt es, wenn die Körper der Funktion eingegeben wird. Für Objekte in Namespacebereich beginnt Lebensdauer bei Programmstart . Das gleiche gilt für statische Objekte der Klasse -umfang:

class A {
    static string classScopeA;
};

string A::classScopeA;

A a, b; &a.classScopeA == &b.classScopeA == &A::classScopeA;

Wie Sie sehen, ist classScopeA nicht auf bestimmte Objekte seiner Klasse gebunden, sondern auf die Klasse selbst. Die Adresse aller drei Namen, die oben das gleiche ist, und alle das gleiche Objekt bezeichnen. Es gibt spezielle Regel, wann und wie statische Objekte initialisiert werden, aber lasst uns jetzt nicht Sorge darüber. Das ist unter dem Begriff statisches Initialisierungsreihenfolge Fiasko .


Dynamische Speicherdauer

Die letzte Speicherdauer ist dynamisch. Sie verwenden es, wenn Sie Objekte auf einer anderen Insel leben zu wollen, und Sie wollen Zeiger um, dass setzen auf sie verweisen. Sie nutzen sie, auch wenn Ihre Objekte sind groß , und wenn Sie möchten Arrays der Größe nur schaffen bekannt Laufzeit . Aufgrund dieser Flexibilität, Objekte mit dynamischer Speicherdauer sind kompliziert und langsam zu verwalten. Objekte mit, dass dynamische Dauer Lebensdauer beginnen, wenn eine entsprechende neue Operator Aufruf geschieht:

int main() {
    // the object that s points to has dynamic storage 
    // duration
    string *s = new string;
    // pass a pointer pointing to the object around. 
    // the object itself isn't touched
    foo(s);
    delete s;
}

void foo(string *s) {
    cout << s->size();
}

Die Lebensdauer endet erst, wenn Sie anrufen löschen für sie. Wenn Sie vergessen, dass am Ende die Objekte nie Lebensdauer. Und Klassenobjekte, die einen Benutzer erklärt Konstruktor definieren nicht haben ihre Destruktoren aufgerufen. Objekte mit dynamischer Speicherdauer erfordern die manuelle Handhabung ihres Lebens und die damit verbundene Speicherressource. Bibliotheken existieren von ihnen Gebrauch zu erleichtern. Explicit Garbage Collection für bestimmte Objekte kann mit einem intelligenten Zeiger festgelegt werden:

int main() {
    shared_ptr<string> s(new string);
    foo(s);
}

void foo(shared_ptr<string> s) {
    cout << s->size();
}

Sie müssen über Aufruf löschen nicht kümmern: Die gemeinsame ptr für Sie tut es, wenn der letzte Zeiger, der das Objekt verweist auf den Gültigkeitsbereich verlässt. Der gemeinsam genutzte ptr selbst verfügt über eine automatische Speicherdauer. So seine Lebensdauer wird automatisch verwaltet, so dass es prüfen, ob er die spitze auf dynamisches Objekt in seinem destructor löschen sollte. Für Shared_ptr Referenz siehe boost Dokumente: http://www.boost.org/doc/libs/1_37_0/libs/smart_ptr/shared_ptr.htm

Es ist gesagt worden, aufwendig, ebenso wie „die kurze Antwort“:

  • statische Variable (Klasse)
    Lebensdauer = Programmlaufzeit (1)
    Sichtbarkeit = bestimmt durch Zugriffsmodifikatoren (privat / protected / public)

  • statische Variable (global scope)
    Lebensdauer = Programmlaufzeit (1)
    = Sichtbarkeit der Übersetzungseinheit wird instanziiert in (2)

  • heap Variable
    Lebensdauer = definiert durch Sie (neu löschen)
    Sichtbarkeit = definiert durch Sie (was auch immer Sie den Zeiger zuweisen)

  • stapeln Variable
    Sichtbarkeit = von der Deklaration bis zum Umfang verlassen wird
    Lebensdauer = von der Deklaration bis zum Umfang erklärt verlassen


(1) genauer: von der Initialisierung bis Deinitialisierung der Übersetzungseinheit (d.h. C / C ++ Datei). Reihenfolge der Initialisierung von Übersetzungseinheiten nicht durch den Standard definiert ist.

(2) Achtung:., Wenn Sie eine statische Variable in einem Header instanziiert, jede Übersetzungseinheit erhält eine eigene Kopie

Ich bin sicher, dass eine der Pedanten mit einer besseren Antwort kommt in Kürze, aber der Hauptunterschied ist die Geschwindigkeit und Größe.

Stapel

Drastische schneller zuzuordnen. Es ist in O (1) durchgeführt, da es zugeordnet ist, wenn der Stapelrahmen einzurichten, so dass es im wesentlichen frei ist. Der Nachteil ist, dass, wenn Sie aus Stapelspeicher laufen Sie entbeint werden. Sie können die Stack-Größe anpassen, aber IIRC Sie ~ 2MB haben zu spielen. Auch sobald Sie die Funktion alles auf den Stapel verlassen wird gelöscht. So kann es problematisch sein, um es später zu verweisen. (Zeiger zugeordneten Objekte zu stapeln führt zu Fehlern.)

Heap

Drastische langsamer zuzuordnen. Aber Sie haben GB mit zu spielen, und verweisen auf.

Garbage Collector

Der Garbage Collector ist ein Code, der im Hintergrund läuft und gibt Speicher frei. Wenn Sie Speicher auf dem Heap reservieren ist es sehr einfach zu vergessen, sie zu befreien, die als Speicherleck bekannt ist. Im Laufe der Zeit wächst der Speicher Ihre Anwendung verbraucht und wächst, bis es abstürzt. ein Garbage Collector periodisch den Speicher frei haben, hilft Sie nicht mehr benötigen diese Klasse von Bugs zu beseitigen. Natürlich ist dies zu einem Preis kommt, da der Garbage Collector Dinge verlangsamt.

  

Was sind die Probleme der statischen und stapeln?

Das Problem mit „statischen“ Zuordnung besteht darin, dass die Zuordnung zur Compile-Zeit gemacht wird: Sie nicht verwenden können einige variable Anzahl von Daten zuzuordnen, deren Anzahl nicht erst zur Laufzeit bekannt.

Das Problem auf dem „Stack“ mit Zuweisung ist, dass die Zuteilung, sobald das Unterprogramm zerstört wird, die die Zuteilung kehrt der Fall ist.

  

Ich könnte eine ganze Anwendung schreiben, ohne Variablen in den Heap zuweisen?

Vielleicht aber keine nicht-trivial, normal, große Anwendung (aber so genannte "embedded" Programme ohne den Heap geschrieben werden könnten, um eine Teilmenge von C ++).

  

Welche Garbage Collector das?

Es hält Ihre Daten ( „mark und sweep“) beobachten, um zu erkennen, wenn die Anwendung nicht länger es verweist. Dies ist für die Anwendung, da die Anwendung müssen die Daten nicht freigeben ... aber der Garbage Collector kann rechnerisch teuer sein.

Müllsammler sind keine übliche Funktion von C ++ Programmierung.

  

Was könnten Sie selbst den Speicher Sie manipulieren, dass Sie mit dieser Garbage Collector nicht tun könnten?

Die C ++ Mechanismen für deterministische Speicherfreigabe Lernen:

  • 'static': nie freigegeben
  • 'Stack': sobald die Variable "out of scope geht"
  • ‚Halde‘: wenn der Zeiger (explizit gelöscht durch die Anwendung oder implizit gelöscht innerhalb einiger-oder-andere Unterprogramm) gelöscht

Stack-Speicherzuweisung (Funktionsvariablen, lokale Variablen) kann problematisch sein, wenn der Stapel zu „tief“ ist und Sie überfluten den verfügbaren Speicher Zuweisungen zu stapeln. Der Haufen ist für Objekte, die von mehreren Threads oder während des gesamten Programmzyklus zugegriffen werden muss. Sie können auch ohne den Heap ein ganzes Programm schreiben.

Sie können Speicher ganz einfach ohne Garbage Collector auslaufen, aber Sie können auch diktieren, wenn Gegenstände und Speicher freigegeben wird. Ich habe, um Probleme mit Java laufen, wenn es die GC läuft und ich habe einen Echtzeit-Prozess, weil der GC einen exklusiven Faden (nichts anderes laufen kann). Also, wenn die Leistung entscheidend ist und Sie können es garantiert keine durchgesickert Objekte sind, kein GC ist sehr hilfreich. Ansonsten einfach macht es Ihnen das Leben hassen, wenn die Anwendung Speicher verbraucht und Sie müssen die Quelle eines Lecks aufzuspüren.

Was passiert, wenn Ihr Programm nicht im Voraus nicht wissen, wie viel Speicher zuweisen (daher können Sie nicht Stack-Variablen verwenden). Sprich verkettete Listen, können die Listen ohne zu wissen, im Voraus wachsen, was seine Größe ist. So auf einem Haufen Zuweisung macht Sinn für eine verknüpfte Liste, wenn Sie nicht wissen, wie viele Elemente in sie eingefügt werden würde.

Ein Vorteil der GC in manchen Situationen ist ein Ärgernis in anderen; Vertrauen auf GC Denken fördert nicht viel darüber. In der Theorie wartet, bis ‚Leerlauf‘ Zeit oder bis es absolut muss, wenn es die Bandbreite stehlen und Antwortlatenz in Ihrer Anwendung verursachen.

Aber Sie müssen nicht ‚nicht darüber nachdenken.‘ Genau wie bei allem anderen in Multithreaded-Anwendungen, wenn Sie liefern können, können Sie ergeben. So zum Beispiel, in .Net, ist es möglich, eine GC zu verlangen; indem Sie dies tun, statt weniger häufig längere Lauf GC, können Sie häufiger kürzere Lauf GC haben und die Latenzzeit mit diesem Overhead, der sich auszubreiten.

Aber diese besiegt die Hauptattraktion von GC, das zu sein scheint „ermutigt, nicht zu denken viel darüber, weil es Auto-Matte-ic ist.“

Wenn Sie zum ersten Mal in die Programmierung ausgesetzt waren, bevor GC verbreitet wurde und waren komfortabel mit malloc / free und neu / löschen, dann könnte es sogar sein, dass Sie GC finden ein wenig ärgerlich und / oder mißtrauisch sind (wie man sein könnte misstrauen ‚Optimierung‘, die eine bewegte Geschichte hinter sich hat.) Viele Anwendungen tolerieren zufällige Latenz. Aber für Anwendungen, die dies nicht tun, wo zufällige Latenzzeit weniger akzeptabel, eine gemeinsame Reaktion ist GC-Umgebungen zu vermeiden und in Richtung rein unmanaged Code bewegen (oder, Gott bewahre, eine langen aussterbende Kunst, Assemblersprache.)

Ich hatte einen Sommer Student hier eine Weile zurück, einen Praktikanten, kluges Kind, die auf GC entwöhnt wurden; er war so adament über die Ueberlegenheitsgefuehl von GC, die, selbst wenn in nicht verwalteten C Programmierung / C ++ er weigerte sich, die malloc / free neue / Löschen Modell zu folgen, weil, Zitat, „Sie sollten diese nicht in einer modernen Programmiersprache zu tun haben.“ Und du weißt? Für kleinen, kurzen Lauf Apps, Sie in die Tat mit dem weg erhalten können, aber nicht für lange performante Anwendungen ausgeführt wird.

Stack ist ein Speicher vom Compiler zugewiesen, wann immer wir das Programm kompiliert, in Standard-Compiler etwas Speicher von OS zuordnet (wir können die Einstellungen von Compiler-Einstellungen in Ihrem IDE ändern) und OS ist diejenige, die Sie den Speicher geben , dessen hängt von vielen verfügbaren Speicher auf dem System und viele andere Dinge, und Speicher zu stapeln kommende zuteilen, wenn wir eine Variable sie kopieren (ref als Formalen) diese Variablen aufgeschoben stapeln sie folgen einige Namenskonventionen standardmäßig seine CDECL erklären in Visual Studio ex: Infixschreibweise: c = a + b; der Stapel schiebt rechts nach links DRüCKT, b zu stapeln, Operator getan, ein zu stapeln und Ergebnis die, i, e c zu stapeln. In Pre-Fix-Notation: = + Cab Hier werden alle Variablen geschoben 1. zu stapeln (von rechts nach links), und dann werden die Operation gemacht.  Dieser Speicher durch Compiler zugeordnet ist festgelegt. So können 1MB Speicher übernehmen für unsere Anwendung zugeordnet wird, können Variablen verwendet 700kb Speicher sagen (alle lokalen Variablen geschoben werden, stapeln, wenn sie dynamisch zugewiesen werden), so verbleibenden 324KB Speicher Heap zugewiesen wird. Und dieser Stapel hat weniger Lebenszeit, wenn der Umfang der Funktion endet dieser Stapel gelöscht wird.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top