Schwarze Magie hinter den Kulissen des GCC?



  • Hattet ihr schonmal einen Instanzenzähler mit einem Wert von -36? Ich schon. Im Prinzip versuche ich nur, einen zweidimensionalen Vektor mit Instanzen einer einfachen Klasse zu füllen. Dabei melden sich der Konstruktor und Destruktor jeweils mit std::cout zu Wort.

    Langer Rede kurzer Sinn, hier mein Code:

    #include <vector>
    #include <iostream>
    
    #define WIDTH 5
    #define HEIGHT 3
    
    int instancecounter;
    
    class hasZahl {
    	public:
    		int x;
    		hasZahl (int a) {
    			instancecounter++;
    			x=a;
    			std::cout << "c: " << a << "  ";
    		}
    		~hasZahl () {
    			instancecounter--;
    			std::cout << "d: " << x << "  ";
    		}
    };
    
    int main ()
    {
    	instancecounter=0;
    	std::vector<std::vector<hasZahl> > V;
    	std::vector<hasZahl>* B;
    	for (int i=0; i<WIDTH; i++)
    	{
    		B = new (std::vector<hasZahl>);
    		for (int j=0; j<HEIGHT; j++)
    		{
    			B->push_back (*(new hasZahl(10*i+j)));
    		}
    		std::cout << "\npush_back col " << i << std::endl;
    		V.push_back(*B);
    		std::cout << "\npush_back col " << i << " done" << std::endl;
    	}
    	std::cout << std::endl;
    	for (int y=0; y<HEIGHT; y++)
    	{
    		for (int x=0; x<WIDTH; x++)
    		{
    			std::cout << V.at(x).at(y).x << " ";
    		}
    		std::cout << std::endl;
    	}
    	std::cout << "\nexisting instances: " << instancecounter << "\n" ;
    	V.clear();
    	std::cout << "\ndone\n";
    	std::cout << "\nexisting instances: " << instancecounter << "\n" ;
    	return 0;
    }
    

    Und hier das (sehr merkwürdige) Ausgabeverhalten:

    c: 0  c: 1  d: 0  c: 2  d: 0  d: 1  
    push_back col 0
    
    push_back col 0 done
    c: 10  c: 11  d: 10  c: 12  d: 10  d: 11  
    push_back col 1
    d: 0  d: 1  d: 2  
    push_back col 1 done
    c: 20  c: 21  d: 20  c: 22  d: 20  d: 21  
    push_back col 2
    d: 0  d: 1  d: 2  d: 10  d: 11  d: 12  
    push_back col 2 done
    c: 30  c: 31  d: 30  c: 32  d: 30  d: 31  
    push_back col 3
    
    push_back col 3 done
    c: 40  c: 41  d: 40  c: 42  d: 40  d: 41  
    push_back col 4
    d: 0  d: 1  d: 2  d: 10  d: 11  d: 12  d: 20  d: 21  d: 22  d: 30  d: 31  d: 32  
    push_back col 4 done
    
    0 10 20 30 40 
    1 11 21 31 41 
    2 12 22 32 42 
    
    existing instances: -21
    d: 0  d: 1  d: 2  d: 10  d: 11  d: 12  d: 20  d: 21  d: 22  d: 30  d: 31  d: 32  d: 40  d: 41  d: 42  
    done
    
    existing instances: -36
    

    Warum ist das alles so seltsam? Wie ist das mit anderen Compilern? 😕



  • *hab mich verlesen*



  • Stichwort: Kopierkonstruktor.



  • Desweiteren erstellst du ein Element mit new, dereferenzierst es und speicherst dann die Kopie davon in den Vector. Memory Leak.



  • Kurz: Sämtliche Verwendungen von Pointern bei dir sind FALSCH:



  • C++ generiert fuer dich einen vorgefertigten Kopierkonstruktor, wenn du keinen angibst oder es explizit verbietest.


  • Mod

    edit: Ach, dummer Fehler meinerseits. Der Kopierkonstruktor ist es, was fehlt.

    edit2: Die Verwendung der Pointer ist ungewöhnlich (ehemaliger Java-Programmierer?), aber nicht grundsätzlich falsch. Mit automatischen Variablen kommt das gleiche raus, bloß ohne dass man am Programmende aufräumen muss (was der Threadersteller nicht tut -> Speicherloch).



  • Fellhuhn schrieb:

    Stichwort: Kopierkonstruktor.

    Aaah jetzt ergibt das alles viel mehr Sinn.

    Fellhuhn schrieb:

    Desweiteren erstellst du ein Element mit new, dereferenzierst es und speicherst dann die Kopie davon in den Vector. Memory Leak.

    Mir war nicht klar, dass die Zuweisung in einen Vektor das Element kopiert.

    Fellhuhn schrieb:

    Kurz: Sämtliche Verwendungen von Pointern bei dir sind FALSCH:

    Entschuldige bitte, ich komme eigentlich aus der C-Welt und habe mit C++ nicht viel Erfahrung. Wie sollte ich es besser machen?

    Ich hab mal einen neuen Versuch gestartet:

    #include <vector>
    #include <iostream>
    
    #define WIDTH 5
    #define HEIGHT 3
    
    int instancecounter;
    
    class hasZahl {
    	public:
    		int x;
    		hasZahl (int a)
    		{
    			instancecounter++;
    			x=a;
    			std::cout << "c: " << a << "  ";
    		}
    		~hasZahl ()
    		{
    			instancecounter--;
    			std::cout << "d: " << x << "  ";
    		}
    		hasZahl (const hasZahl& alt)
    		{
    			instancecounter++;
    			x = alt.x;
    			std::cout << "cp: " << x << "  ";
    		}
    };
    
    int main ()
    {
    	instancecounter=0;
    	std::vector<std::vector<hasZahl> > V (WIDTH, std::vector<hasZahl> (HEIGHT, 1337));
    	for (int i=0; i<WIDTH; i++)
    	{
    		for (int j=0; j<HEIGHT; j++)
    		{
    			V[i][j].x=10*i+j;
    		}
    	}
    	std::cout << "\n";
    	for (int y=0; y<HEIGHT; y++)
    	{
    		for (int x=0; x<WIDTH; x++)
    		{
    			std::cout << V.at(x).at(y).x << " ";
    		}
    		std::cout << std::endl;
    	}
    	std::cout << "\nexisting instances: " << instancecounter << "\n" ;
    	V.clear();
    	std::cout << "\ndone\n";
    	std::cout << "\nexisting instances: " << instancecounter << "\n" ;
    	return 0;
    }
    

    Jetzt ergibt der Output für mich Sinn. Vielen Dank 🙂

    der output ist btw:

    c: 1337  cp: 1337  cp: 1337  cp: 1337  d: 1337  cp: 1337  cp: 1337  cp: 1337  cp: 1337  cp: 1337  cp: 1337  cp: 1337  cp: 1337  cp: 1337  cp: 1337  cp: 1337  cp: 1337  cp: 1337  cp: 1337  cp: 1337  d: 1337  d: 1337  d: 1337  
    0 10 20 30 40 
    1 11 21 31 41 
    2 12 22 32 42 
    
    existing instances: 15
    d: 0  d: 1  d: 2  d: 10  d: 11  d: 12  d: 20  d: 21  d: 22  d: 30  d: 31  d: 32  d: 40  d: 41  d: 42  
    done
    
    existing instances: 0
    

    EDIT: Moment, die Destroyer-Aufrufe in der ersten Zeile ergeben immernoch keinen Sinn für mich 😕


  • Mod

    Das was du jetzt hast ist ok. Wenn du etwas haben willst, das eher deinem ersten Versuch entspricht, kannst du das so machen:

    #include <vector>
    #include <iostream>
    
    const int WIDTH=5;  // defines sind gefährlich
    const int HEIGHT=3; // Nutze const, dazu ist es da
    
    class hasZahl {
      static int instancecounter;   // Wozu global, wenn es doch zu der Klasse gehört?
    public:
      int x;
      hasZahl (const hasZahl& a) {
        instancecounter++;
        x=a.x;
        std::cout << "cc: " << a.x << "  ";
      }
      hasZahl (int a) {
        instancecounter++;
        x=a;
        std::cout << "C: " << a << "  ";
      }
      ~hasZahl () {
        instancecounter--;
        std::cout << "D: " << x << "  ";
      }
      static int get_count(){return instancecounter;}
    };
    
    int hasZahl::instancecounter=0;
    
    int main ()
    {
      std::vector<std::vector<hasZahl> > V;
      for (int i=0; i<WIDTH; i++)
        {
          std::vector<hasZahl> B;         // Lass den Pointerquatsch
          for (int j=0; j<HEIGHT; j++)
            {
    	  B.push_back (hasZahl(10*i+j));     // Lass den Pointerquatsch
            }
          std::cout << "\npush_back col " << i << std::endl;
          V.push_back(B);
          std::cout << "\npush_back col " << i << " done" << std::endl;
        }
      std::cout << std::endl;
      for (int y=0; y<HEIGHT; y++)
        {
          for (int x=0; x<WIDTH; x++)
            {
    	  std::cout << V.at(x).at(y).x << " ";
            }
          std::cout << std::endl;
        }
      std::cout << "\nexisting instances: " << hasZahl::get_count() << "\n" ;
      V.clear();
      std::cout << "\ndone\n";
      std::cout << "\nexisting instances: " << hasZahl::get_count() << "\n" ;
      return 0;
    }
    

    Ausgabe:

    C: 0  cc: 0  D: 0  C: 1  cc: 1  cc: 0  D: 0  D: 1  C: 2  cc: 2  cc: 0  cc: 1  D: 0  D: 1  D: 2  
    push_back col 0
    cc: 0  cc: 1  cc: 2  
    push_back col 0 done
    D: 0  D: 1  D: 2  C: 10  cc: 10  D: 10  C: 11  cc: 11  cc: 10  D: 10  D: 11  C: 12  cc: 12  cc: 10  cc: 11  D: 10  D: 11  D: 12  
    push_back col 1
    cc: 10  cc: 11  cc: 12  cc: 0  cc: 1  cc: 2  D: 0  D: 1  D: 2  
    push_back col 1 done
    D: 10  D: 11  D: 12  C: 20  cc: 20  D: 20  C: 21  cc: 21  cc: 20  D: 20  D: 21  C: 22  cc: 22  cc: 20  cc: 21  D: 20  D: 21  D: 22  
    push_back col 2
    cc: 20  cc: 21  cc: 22  cc: 0  cc: 1  cc: 2  cc: 10  cc: 11  cc: 12  D: 0  D: 1  D: 2  D: 10  D: 11  D: 12  
    push_back col 2 done
    D: 20  D: 21  D: 22  C: 30  cc: 30  D: 30  C: 31  cc: 31  cc: 30  D: 30  D: 31  C: 32  cc: 32  cc: 30  cc: 31  D: 30  D: 31  D: 32  
    push_back col 3
    cc: 30  cc: 31  cc: 32  
    push_back col 3 done
    D: 30  D: 31  D: 32  C: 40  cc: 40  D: 40  C: 41  cc: 41  cc: 40  D: 40  D: 41  C: 42  cc: 42  cc: 40  cc: 41  D: 40  D: 41  D: 42  
    push_back col 4
    cc: 40  cc: 41  cc: 42  cc: 0  cc: 1  cc: 2  cc: 10  cc: 11  cc: 12  cc: 20  cc: 21  cc: 22  cc: 30  cc: 31  cc: 32  D: 0  D: 1  D: 2  D: 10  D: 11  D: 12  D: 20  D: 21  D: 22  D: 30  D: 31  D: 32  
    push_back col 4 done
    D: 40  D: 41  D: 42  
    0 10 20 30 40 
    1 11 21 31 41 
    2 12 22 32 42 
    
    existing instances: 15
    D: 0  D: 1  D: 2  D: 10  D: 11  D: 12  D: 20  D: 21  D: 22  D: 30  D: 31  D: 32  D: 40  D: 41  D: 42  
    done
    
    existing instances: 0
    

    Wie du siehst, erzeugt deine neue Variante sehr viel weniger unnötiges Kopieren und ist daher vorzuziehen. Allein schon das sofortige Reservieren des Speichers beim Erstellen des Vektors spart bei deiner neuen Variante viel ein. Ich wollte dir nur einmal zeigen, wie man deinen alten Versuch ohne Pointer umsetzt.

    edit2: Edit1 war falsch. Korrigiert.



  • Interessanter Einrück-Stil. Habe ich vorher noch nirgends gesehen.

    SeppJ schrieb:

    std::vector<std::vector<hasZahl> > V;
      for (int i=0; i<WIDTH; i++)
        {
          std::vector<hasZahl> B;
          for (int j=0; j<HEIGHT; j++)
            {
              B.push_back (hasZahl(10*i+j));
            }
          V.push_back(B);
        }
    

    Hmm... ich hätte das so gemacht:

    std::vector<std::vector<hasZahl> > V[b] (WIDTH)[/b];
      for (int i=0; i<WIDTH; i++) {
          std::vector<hasZahl> [b]&[/b] B[b] = V[i][/b];
          for (int j=0; j<HEIGHT; j++) {
              B.push_back (hasZahl(10*i+j));
          }
      }
    

  • Mod

    enormator schrieb:

    EDIT: Moment, die Destroyer-Aufrufe in der ersten Zeile ergeben immernoch keinen Sinn für mich 😕

    Nochmal sauber erklärt:
    Zuerst wird ein temporäres Objekt erzeugt, mit Wert 1337. Dies ist die Vorlage das Element mit dem später der "innere" Vektor gefüllt werden soll.

    c: 1337
    

    Dieses wird 3x in einen temporären Vektor kopiert. Dieser temporäre Vektor ist die Vorlage für den "inneren" Vektor.

    c: 1337 cp: 1337 cp: 1337 cp: 1337
    

    Das temporäre Element 1337 hat seine Schuldigkeit getan, das temporäre Element kann gehen:

    c: 1337 cp: 1337 cp: 1337 cp: 1337 d: 1337
    

    Der temporäre Vektor wird 5x in den "äußeren" Vektor kopiert. Dies ist der eigentliche Vektor mit dem am Ende gearbeitet wird. Er trägt den Namen V . 5 mal 3 Elemente zu kopieren gibt 15 Kopierkonstruktoraufrufe:

    c: 1337  cp: 1337  cp: 1337  cp: 1337  d: 1337  cp: 1337  cp: 1337  cp: 1337  cp: 1337  cp: 1337  cp: 1337  cp: 1337  cp: 1337  cp: 1337  cp: 1337  cp: 1337  cp: 1337  cp: 1337  cp: 1337  cp: 1337
    

    Dann hat der temporäre Vektor der Länge 3 seine Schuldigkeit getan und sein Destruktor wird aufgerufen. Dieser ruft den Destruktor der 3 Elemente auf, die er enthält:

    c: 1337  cp: 1337  cp: 1337  cp: 1337  d: 1337  cp: 1337  cp: 1337  cp: 1337  cp: 1337  cp: 1337  cp: 1337  cp: 1337  cp: 1337  cp: 1337  cp: 1337  cp: 1337  cp: 1337  cp: 1337  cp: 1337  cp: 1337  d: 1337  d: 1337  d: 1337
    

    Fertig!


  • Mod

    krümelkacker schrieb:

    Hmm... ich hätte das so gemacht:

    std::vector<std::vector<hasZahl> > V[b] (WIDTH)[/b];
      for (int i=0; i<WIDTH; i++) {
          std::vector<hasZahl> [b]&[/b] B[b] = V[i][/b];
          for (int j=0; j<HEIGHT; j++) {
              B.push_back (hasZahl(10*i+j));
          }
      }
    

    Hatte ich. Dafür braucht es aber einen Defaulkonstruktor.

    Der Einrückstil: Ups, dass passiert, wenn man direkt aus Emacs C&P benutzt. Emacs benutzt intern nämlich eine Mischung aus Tab und Space für die Einrückung. Das Forum kommt mit den Tabs wohl nicht zurecht oder hat eine andere Tabbreite.



  • krümelkacker schrieb:

    Hmm... ich hätte das so gemacht:

    std::vector<std::vector<hasZahl> > V[b] (WIDTH)[/b];
      for (int i=0; i<WIDTH; i++) {
          std::vector<hasZahl> [b]&[/b] B[b] = V[i][/b];
          for (int j=0; j<HEIGHT; j++) {
              B.push_back (hasZahl(10*i+j));
          }
      }
    

    Die Version gefällt mir sehr gut. wobei vielleicht noch ein B.reserve (HEIGHT) angebracht wäre, wenn ich das richtig verstanden habe:

    std::vector<std::vector<hasZahl> > V (WIDTH);
      for (int i=0; i<WIDTH; i++) {
          std::vector<hasZahl> & B = V[i];
          B.reserve (HEIGHT);
          for (int j=0; j<HEIGHT; j++) {
              B.push_back (hasZahl(10*i+j));
          }
      }
    

    @SeppJ: Vielen Dank für die Erklärung. Mir ist das nun alles viel klarer geworden 👍


Log in to reply