Einige Fragen zu C++



  • Hi,

    Ich arbeite jetzt schon eine ganze Weile mit C++ aber trotzdem gibt es noch ein paar Dinge, die ich mich schon lange gefragt, aber noch keine Antowrt darauf gefunden habe.

    Vorab, ich schreibe seit jeher eigentlich nur code mit MSVC 6.0. Deswegen kann es natürlich sein, das ich hier fragen stelle die nach aktuellem Standard absoluter Rotz sind, oder einfach sondereigenheiten von MSVC.

    *************************** 1 ************************
    Übergebene Parameter und Lokale Variablen landen ja normalerweise auf dem Stack. Diese werden dann ja normalerweise über den (Stackpointer - x) von der Funktion referenziert. Das ist in C ja auch okay so, aber wie funktioniert das in C++, wo nicht alle Objekte am Anfang deklariert werden müssen. Werden diese Trotzdem alle vorher auf dem Stack angelegt?

    Beispiel

    void func()
    {
      int i=0; // Von mir aus SP-4
      /* irgendein code */
      int j=0;
      i=5; // Wenn j wirklich erst vor dieser Answeisung auf dem Stack 
           // angelegt wurde, müsste i doch jetzt eigentlich über SP-8 
           // referenziert werden, oder? 
    }
    

    Es geht mir eben auch darum, ob ich mit dieser Konstruktion Stackspeicher sparen kann oder nicht.

    void func()
    {
      int i;
      if (/* Tritt of ein */) return;
    
      /* .... */
      CVeryBigObject Object;
      /* ... */
    }
    

    ************************** 2 *************************

    void func()
    {
      int i=0;
      if (1)
      {
        int i=5;
        count << i; // 5
      }
      cout << i; // 0
    }
    

    Diese konstrukt hier macht mich richtig fertig. Warum geht das überhaupt?

    ************************* 3 *************************

    void func()
    {
      int i=0;
      for (i=0; i<1000; i++)
      {
        CMeinObjekt Objekt;
        /* ... */
      }
    }
    

    Wird hierbei wirklich jedes mal das Objekt erstellt, zerstört? Eigentlich muss es ja so sein, damit ich bei jedem Schleifenstart wirklich mit einem frischen Objekt arbeiten kann....

    ************************ ENDE *************************

    Würde mich wirklich freuen, wenn mich mal jemand aufklären könnte.

    Vielen dank im voraus
    template



  • Hi,

    also den Heap gibt es ja auch noch.
    Immer, wenn ich nicht raten möchte, dann stelle ich den Visual Compiler in den Projekteinstellungen auf "Ausgabe: ASM Listing mit Funktionsnamen generieren" und schaue es mir mit eigenen Augen an, was der (womöglich optimierende) Compiler draus macht.



  • template schrieb:

    Es geht mir eben auch darum, ob ich mit dieser Konstruktion Stackspeicher sparen kann oder nicht.

    void func()
    {
      int i;
      if (/* Tritt of ein */) return;
    
      /* .... */
      CVeryBigObject Object;
      /* ... */
    }
    

    Ja, damit kannst du dir was sparen.

    template schrieb:

    void func()
    {
      int i=0;
      if (1)
      {
        int i=5;  // überlagert i von vorhin
        cout << i; // 5
        cout << ::i; // 0 so kommste wieder an das Alte
      }
      cout << i; // 0
    }
    

    Diese konstrukt hier macht mich richtig fertig. Warum geht das überhaupt?

    Das funktioniert, weil du das in Blöcke unterteilt hast.
    Es geht ja "IMMER" um den Scope - also das was man sieht.
    Das neue i gibt's nur in dem Anweisungsblock. Dort wird es erstellt und am Ende auch wieder zerstört.
    Darum wäre so etwas klüger (analog folgendes Beispiel)

    void func() {
      VeryBigObject a;
      while(true) {
       a.erase(); // a.empty() oder ...
       /* ... */
      }
    }
    

    template schrieb:

    void func()
    {
      int i=0;
      for (i=0; i<1000; i++)
      {
        CMeinObjekt Objekt;
        /* ... */
      }
    }
    

    Wird hierbei wirklich jedes mal das Objekt erstellt, zerstört? Eigentlich muss es ja so sein, damit ich bei jedem Schleifenstart wirklich mit einem frischen Objekt arbeiten kann....

    Ja, es wird jedesmal neu erstellt und zerstört.
    Das kommt vom neuen "Block" der Forschleife.


  • Mod

    prinzipiell sind diese fragen nicht durch den standard beantwortbar, es ist sogar denkbar, dass die antwort kontextabhängig ist - z.b. abhängig von den optimierungseinstellungen.

    void func()
    {
      int i=0; // Von mir aus SP-4
      /* irgendein code */
      int j=0;
      i=5; // Wenn j wirklich erst vor dieser Answeisung auf dem Stack 
           // angelegt wurde, müsste i doch jetzt eigentlich über SP-8 
           // referenziert werden, oder? 
    }
    

    Es geht mir eben auch darum, ob ich mit dieser Konstruktion Stackspeicher sparen kann oder nicht.

    es ist überhaupt nicht sicher, ob speicher auf dem stack reserviert wurde, die variablen könnten sich ohne weiteres nur in registern befinden - oder ganz wegoptimiert worden sein. selbst wenn sie sich auf dem stack befinden, könnten sie denselben speicherplatz benutzen, falls der optimierer die tatsächliche lebensdauer (der pseudo-destruktor von PODs ist ja trivial und kann in sofern zu jedem zeitpunkt, nachdem die variable das letzte mal referenziert wurde, 'ausgeführt' werden) hinreichend genau bestimmen kann.

    void func()
    {
      int i;
      if (/* Tritt of ein */) return;
    
      /* .... */
      CVeryBigObject Object;
      /* ... */
    }
    

    es ist denkbar, dass der speicher für Object erst nach dem if reserviert wird, allerdings ist das wohl selten der fall. es ist ja erheblich simpler, gleich am anfang einmalig genügend speicher für alle variablen und möglichen ausführungspfade zu reservieren. speicherreservierung ist nicht dasselbe wie initialisierung - der konstruktor von Object wird trotzdem erst dort ausgeführt, wo es definiert ist.

    void func()
    {
      int i=0;
      if (1)
      {
        int i=5;
        count << i; // 5
      }
      cout << i; // 0
    }
    

    Diese konstrukt hier macht mich richtig fertig. Warum geht das überhaupt?

    namen sind für den compiler schall und rauch, das i im inneren scope ist für den compiler etwas völlig anderes als das äussere. es gibt zwar kein sprachmittel, im inneren scope auf das äussere i zuzugreifen, der compiler wäre aber dazu in der lage.

    void func()
    {
      int i=0;
      for (i=0; i<1000; i++)
      {
        CMeinObjekt Objekt;
        /* ... */
      }
    }
    

    Wird hierbei wirklich jedes mal das Objekt erstellt, zerstört? Eigentlich muss es ja so sein, damit ich bei jedem Schleifenstart wirklich mit einem frischen Objekt arbeiten kann....

    ja, falls das objekt allerdings ein POD ist, passiert im grunde gar nichts (der standard schreibt zwar nichts vor, ich wette aber, dass man in der praxis bei jedem schleifendurchgang das ergebnis des vorherigen durchganges vorfindet - es sei denn, der compiler betreibt loop-unrolling: in diesem falle könnte die funktion ein mehrfaches des platzes eines einzigen CMeinObjekt beanspruchen).
    unterm strich: der compiler hat hier so gut wie völlig freie hand.



  • Zu 1: Auf solche Sachen würde ich mich nicht einlassen. Wenn es dir um Speicherplatz geht, dann gehe lieber hin und setzte den folgenden Code in Klammern {}. Alles was in einem Block deklariert ist wird erst beim Erreichen von { angelegt und bei erreichen von } zerstört..
    Zu 2: Das geht weil int i = 5; in einem Block definiert ist. Die Variable wird bei { angelegt und bei } zerstört. Wenn innerhalb des Blocks auf i zugegriffen wird, wird zuerst im Block nach einer Variable mit diesem Namen gesucht und dann in dem nächsten übergeordneten Block usw. .
    Wird der Block verlassen existiert nur noch int i = 0; Variable ergo kann auch nur diese ausgegeben werden.
    Zu 3: Kann ja nicht anders sein als dass er jedesmal den Destuktor und Konstruktor aufruft. Alle Variablen innerhalb eines Blocks werden beim Eintritt in den Block ({}) angelegt und bei dessen verlassen zerstört...


  • Mod

    Zu 1: Auf solche Sachen würde ich mich nicht einlassen. Wenn es dir um Speicherplatz geht, dann gehe lieber hin und setzte den folgenden Code in Klammern {}. Alles was in einem Block deklariert ist wird erst beim Erreichen von { angelegt und bei erreichen von } zerstört..

    das genügt nicht. man müsste mindestens den betreffenden teil in eine eigene funktion auslagern - und dann um sicher zu gehen auch noch inlining verhindern.

    Zu 3: Kann ja nicht anders sein als dass er jedesmal den Destuktor und Konstruktor aufruft. Alle Variablen innerhalb eines Blocks werden beim Eintritt in den Block ({}) angelegt und bei dessen verlassen zerstört...

    anlegen im sinne von initialisieren geschieht dort, wo die definition steht. speicher- bzw. registerreservierung kann zu einem beliebigen zeitpunkt davor geschehen.



  • Du hast natürlich recht Camper, nur würde ich niemanden raten so zu programmieren. Man überlistet sich mit Anwendungen, die man zu sehr von der Implementierung des Compilers abhängig macht meistens selber.
    Falls der Compiler zu verschwenderisch mit dem Speicher umgeht, dann hat man einen für das Programm ungeeigneten Compiler.



  • Vielen dank für die zahlreichen antworten. Ja klar die gültigkeitsdauer einer Variable bezieht sich auf den Scope in dem sie deklariert wurde, das ist logisch. Also ist es ja wirklich auch logisch, diese am anfang des Scopes zu erstellen. Und die scopes hirachisch rückwerts zu durchwandern um die Variablenbezeichner variablen zuzuordnen, das macht auch sinn.


Anmelden zum Antworten