C++ Anfänger: Objekte erzeugen?



  • Noch eine Bemerkung:

    QWidget w = new QWidget();
    

    dürfte nicht klappen, weil new einen Zeiger zurückliefert. Also eher ganz normal wie immer

    QWidget* w = new QWidget();
    

    Wenn du wirklich etwas über Zeiger, new etc lernen willst, solltest du aber vielleicht nicht mit QT üben (guck mal in der Doku "Object Model").



  • wob schrieb:

    In C++ kannst du Objekte wahlweise auf dem Stack oder auf dem Heap erzeugen.

    Kennt C++ die Konzepte Stack und Heap?

    Im Draft-PDF finde ich diese beiden Begriffe nicht, wie hier z. B. unter "Automatic storage duration":

    http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3690.pdf schrieb:

    3.7.3 Automatic storage duration

    Block-scope variables explicitly declared register or not explicitly declared static or extern have automatic storage duration. The storage for these entities lasts until the block in which they are created exits.



  • Mittagspause schrieb:

    Noch eine Bemerkung:

    QWidget w = new QWidget();
    

    dürfte nicht klappen, weil new einen Zeiger zurückliefert. Also eher ganz normal wie immer

    QWidget* w = new QWidget();
    

    Wenn du wirklich etwas über Zeiger, new etc lernen willst, solltest du aber vielleicht nicht mit QT üben (guck mal in der Doku "Object Model").

    Das ist richtig. Das "*" hatte ich vergessen. Genau dies ist so eine Sache, die mich nach den ersten Tutorials sofort irritiert hat, weil es i.d.R. nicht erklärt wird (eine Ausnahme habe ich nach langer Suche gefunden).

    EinTyp* var; 
    EinTyp *var; // gleiches Ergebnis
    

    Danke für die Erklärung mit Stack/Heap! Mit smart pointers werde ich mich mal beschäftigen. Jetzt stellt sich noch die Frage, wann ich direkt mit einem Objekt und wann mit einem Zeiger auf ein Objekt arbeiten sollte? Bei QT wird da anscheinend auch ziemlich gemixt.

    Mit dem Üben hast du natürlich recht: Ich probiere i.d.R. Dinge auch zuerst in einer separaten Konsolenanwendung aus.

    Gruß,
    temi


  • Mod

    temi schrieb:

    Genau dies ist so eine Sache, die mich nach den ersten Tutorials sofort irritiert hat, weil es i.d.R. nicht erklärt wird (eine Ausnahme habe ich nach langer Suche gefunden).

    Internettutorials zu C++ für Anfänger sind praktisch alle für die Tonne. C++ ist eine sehr komplexe Sprache mit vielen Fallstricken. Ein Lehrer für Anfänger braucht daher den vollen Durchblick, damit er kein gefährliches Halbwissen vermittelt. Internettutorialschreiber sind aber in der Regel keine Experten, sonst würden sie kein Internettutorial schreiben.



  • Infragesteller schrieb:

    Kennt C++ die Konzepte Stack und Heap?

    Du hast natürlich recht, C++ redet nur von Speicherdauer, aber nicht vom Ort. Nur praktisch ist es oft so, was man sehen kann, wenn man viele große Objekte mit "automatic storage duration" in einer rekursiv aufgerufenen Funktion hat. Dann bekommt man deutlich schneller einen Stack Overflow als wenn man sich den Speicher jeweils mit make_unique holt.

    Das muss aber nicht für alle Systeme gelten, vielleicht ist das irgenwie im Embedded-Bereich gerne mal anders. Ich werde in Zukunft dran denken, das richtig zu formulieren.

    Der Standard kennt aber zumindest "Stack Unwinding", ist das ggf. ein Hinweis darauf, dass automatic storage duration oft so implementiert ist?

    Insgesamt gilt: Halbwissen in diesem Text vorhanden 😉



  • SeppJ schrieb:

    Internettutorials zu C++ für Anfänger sind praktisch alle für die Tonne.

    Darum hab ich jetzt auch "Der C++ Programmierer" von Breymann. Scheint recht umfassend zu sein und für blutige Anfänger schon sehr herausfordernd. Das Problem ist hier natürlich die passende Stelle zu finden, die ein Problem lösen könnte 🙂



  • wob schrieb:

    Infragesteller schrieb:

    Kennt C++ die Konzepte Stack und Heap?

    Du hast natürlich recht [...] Halbwissen in diesem Text vorhanden 😉

    Und wie ist es nun konkret?


  • Mod

    C++, der Sprachstandard, kennt weder Heap noch Stack. Er kennt automatischen und freien Speicher mit jeweils gewissen Anforderungen an diese. Jede real existierende Implementierung des C++-Standards setzt diese Konzepte mittels einer Heap/Stack-Struktur um.



  • Ich würde gerne noch ein paar ergänzende Fragen zu dem Thema stellen:

    1. Wie funktioniert der Mechanismus hinter Stack und Heap, vereinfacht erklärt?

    2. Warum werden Stack-Objekte automatisch freigegeben (erklärt sich evtl. aus 1.)?

    3a. Wie "groß" sind Stack und Heap? (Ist klar, dass es vom installierten Hauptspeicher abhängt. Die Frage soll eher zu 3b hinleiten.)

    3b. Wann erzeuge ich besser Stack-Objekte und wann Heap-Objekte?

    Danke,
    temi



  • 1. https://de.wikipedia.org/wiki/Stapelspeicher
    https://de.wikipedia.org/wiki/Dynamischer_Speicher
    Wenn man ASssembler programmiert, weiß man, das z.B. die CPUs einen Stackpointer (SP) haben. Der wird einfach nur hoch oder runter gezählt bzw. auf die höhere oder niedrigere Speicherzelle gezeigt.

    2. Siehe 1. Natürlich geört bei C++ Objekten etwas mehr dazu als nur einen SP zu verändern. Aber viel Magic macht da die C++ Runtime auch nicht, außer den Destructor zusätzlich aufzurufen.

    3a. Ist entweder vom OS oder Runtime vorgegeben. Oder man konfiguriert das selber.

    3b. Stack-Objekte immer dann wenn möglich, Heap-Objekte nur wenn nötig.



  • Also 1 und 2:

    void f()
    {
        int i; // Speicher mit der Größe von i wird auf dem Stack angelegt
        string s; // Speicher mit der Größe eines leeren Strings wird auf dem Stack angelegt
    
        // Schnickschnack
    
        s = "Blub"; // ?
    
        // mehr Schnickschnack
    } // s wird vom Stack entfernt
      // i wird vom Stack entfernt
    

    Stack-Objekte immer dann wenn möglich, Heap-Objekte nur wenn nötig.

    Stack-Objekte werden am Ende eines Gültigkeitsbereichs (z.B. {}) wieder entfernt, sind also kurzlebiger, während Heap-Objekte solange Speicher belegen, bis sie manuell freigegeben werden.

    Möglich ist ja vieles, aber viele große Objekte sind dann wohl besser auf dem Heap aufgehoben. Ich gehe mal davon aus, dass der Stack eher an seine Grenze stößt, als der Heap.

    Ist das so richtig?

    Sorry, für die vielleicht blöden Fragen, aber bei C# musste ich mich darum nicht kümmern.



  • 1. Wenn std::string auf dem Stack angelegt wird, ist es ja erstmal nicht viel außer der Längen-Variable (wahrscheinlich ein int) und einem Pointer auf den Heap, wo der eigentliche String liegt. D.h. wenn der Inhalt vom String 1 GByte groß wäre, liegt dieser ja nicht auf dem Stack. Die Implementierung selbst benutzt ja wieder dynamischen Speicher, weil der String selbst dynamisch sein soll.

    D.h. der Stack verträgt schon einiges. Wenn du einen Vector auf dem Stack anlegst, werden dessen Elemente trotzdem auf dem Heap angelegt.

    Kritisch wird es, wenn du sehr tiefe Rekursionsaufrufe benutzt, dann kann es mit dem Stack kritisch werden.



  • String, Vector usw. sieht ja ungefähr so aus:

    class String {
    int len; // Stack
    char* content; // Heap, irgendwo steht halt new/delete in der String-klasse
    };
    

    Beim std::vector ist es auch nicht anders. Wahrscheinlich sieht ein String auch so aus:

    class String {
    vector<char> content; // Vector auf dem Stack, aber der char-Kontent wieder im Heap
    };
    

    Das ist also eine kombination aus Stacks und Heaps.

    Manche Klassen haben nur Stack-Members, weil sie undynamisch sind. Aber dann sind sie meistens auch eher klein und belasten sonst den Stack nicht so sehr.



  • Der Vorteil den Stack zu benutzen, liegt nicht nur im Automatismus. Sondern auch darin, das er aufgeräumt ist. Der Heap kann durch viele new und delete fragmentiert werden. Dann wird der Speicher trotzdem knapp mit der Zeit. Im Gegensatz zu .NET und Java kann der Heap nämlich nicht defragmentiert werden. Es gibt zwar Bibliotheken für C++ die das machen können (da wird dann new und delete überladen), aber die sind nicht gängige Technik für C++.

    In C++ müsstest du dein Programm beenden und neu starten, um wieder mit einem sauberen Heap zu erhalten (in .NET/Java bremst der GC halt für Sekunden oder Minuten das Programm aus). Oder man benutzt das Objekt-Pool-Pattern, um die Fragmentierung gering zu halten.

    Kommt am Ende auf die Komplexität deines Programmes an. Ein Server-Prozess, der ein Jahr lang durchlaufen soll, hat da andere Anforderungen an das Stack+Heap-Management als ein Tool das vom User täglich neu gestartet wird.



  • Und jetzt kommen wir zu dem Punkt, wo der infragesteller recht hat: man sollte besser von automatic versus dynamic storage duration sprechen als von Stack/Heap.

    Wenn du hier sagst:

    int i; //Stack
    

    dann muss das nicht unbedingt stimmen. Es kann auch sein, dass diese Variable nur in irgendeinem Register vorgehalten wird und nie auf dem Stack landet. Wichtig ist nur, dass sie am Blockende nicht mehr weiterlebt. Liegt die Variable nur in einem Register, passiert mit irgendwelchen Stackpointern gar nichts.

    Und zu String:
    es gibt sowas wie small string optimization. sizeof(string) ist bei mir z.B. gerade 32. Ein kurzer String kann dann ohne dynamischen Speicher auskommen. Es ist also etwas mehr als nur länge+pointer (aber abhängig von der Implementierung!).

    Also: nimm dynamic storage duration, wenn deine Objekte länger leben sollen als im aktuellen Block ODER wenn deine Objekte variabel groß sind ODER wenn sie sehr groß sind ODER wenn sie groß sind und du z.B. eine rekursive Funktion hast.

    Wie groß Stack/Heap bei dir sind, hängt vom OS und Einstellungen ab (z.B. ulimit unter Linux - sowas steht natürlich nicht im C++-Standard drin). Großer Daumen: Denk bei Stack aber eher an einstellige MB und bei Heap an das, was auf deinem Computer drauf steht (also Größenordnung GB).



  • Ob der Compiler das in ein Register packt, ist doch völlig irrelevant für die Diskussion. Das sind Compiler-spezifische Optimierungen, die man als Programmierer gar nicht beeinflussen kann oder will.

    C++ ist eine HiLevel-Sprache, und wenn wir von Stack reden, ist das eine Hi-Level-Sicht. Wenn es nach deiner Diskussion geht, werden wir nie fertig, weil dann der nächste ankommt, und sagt das seine CPU das aber doch anders macht.



  • ich glaube, wob bezieht sich da mehr auf SeppJ

    wob schrieb:

    man sollte besser von automatic versus dynamic storage duration sprechen als von Stack/Heap.

    denn stack und heap kennt der standard nicht. storage duration hingegen schon - und ist auch in deinem sinn (hilevel) besser.



  • Warum eigentlich der Zwang in Java, alles aus dem Heap ... eh mit dynmischer Speicherzeit anzulegen?

    Weil der GC so gut ist, dass er damit umgehen kann?
    Real hab ich bei Server-Anwendungen aber schön öfter miterlebt, dass der GC enorm arbeiten muss und Zeit/Ressourcen frisst. Das hätte man im Fall von "mehr auf Stack" ... eh mehr mit automatischer Speicherzeit doch zumindestens etwas verhindert?

    Gabs dazu ein guten Grund oder ist es halt so?



  • Artchi schrieb:

    C++ ist eine HiLevel-Sprache, und wenn wir von Stack reden, ist das eine Hi-Level-Sicht. Wenn es nach deiner Diskussion geht, werden wir nie fertig, weil dann der nächste ankommt, und sagt das seine CPU das aber doch anders macht.

    Genau deshalb sollten wir ja überhaupt nicht von Stack reden.
    Die Hi-Level Abstraktion ist automatic vs dynamic.

    Die Maximalgrösse des Stacks unter Win32 ist übrigens 1GB, wobei die Grösse vom Linker festgelegt wird, mit einem Defaultwert von 1MB.



  • Also Automatic anstatt Stack als Begriff kann ich ja noch konform gehen. Soll mir Recht sein.

    @gezwungen! In Java wird alles (Objekte) auf den Heap gepackt, weil es keine statische Kompilierung gibt. Weil man weiß nie vorher, wie groß ein Objekt ist. Bei C++ brauchst du z.B. die Header-Dateien genau dafür, und wehe du änderst eine Header... das ist bei Java/.NET nicht so.
    Es soll aber bald in Java die sogenannten Value-Types (?) geben, die werden auch auf dem Stack... ehm, automatischen Speicher abgelegt werden.

    Bei Java kann man auch nicht von dem einen GC-Verhalten sprechen, da es ja mehrere GC-Strategien gibt. Die Server-JVM macht in viel höherer Frequenz Speicherbereinigungen, damit diese möglichst nur kurz das System blockieren (damit der Server immer antwortfähig ist).
    Die Client-JVM dagegen springt nur selten an, dafür dauert eine Speicherbereinigung länger und blockiert sichtbar das Programm.

    Aber man kann sich in Java zwischen diesen und anderen GCs beim Start der JVM entscheiden.


Anmelden zum Antworten