Probleme mit dynamischen Arrays (malloc und free)


  • Mod

    Der Compiler kann (offiziell) nicht in deine Funktionen gucken. Der sieht nur die Signatur deiner Multiplikationsfunktion. Die nimmt nicht-const-Referenzen als Argument. Das heißt, potentiell könnte diese Funktion ihre Argumente ändern, im Sinne von:

    void foo(int &i) { i=4;}
    

    Jetzt willst du diese Funktion auf das temporäre Zwischenergebnis deiner Rechnung anwenden. Das wäre im obigen Beispiel ungefähr so wie

    foo(3 * 7);
    

    Und das wäre ja totaler Quatsch, da du nicht das Ergebnis von 3 * 7 auf 4 setzen kannst. Und da der Compiler nicht ausschließen kann, dass dein Multiplikationsoperator so etwas nicht tut, lässt er das nicht zu. Wäre im oberen Beispiel hingegen

    void foo (const int &i) {...}
    

    dann wüsste er ganz sicher, dass i nicht geändert wird, und er könnte foo sorgenlos auf konstante Ausdrücke anwenden.



  • Alle erzeugten Matrix-Instanzen sind Objekte und können referenziert werden (egal ob direkt oder als temporäre Ausdrücke).

    Der wichtige Unterschied bzgl. der Parameter Matrix und Matrix & (bzw. const Matrix &) ist, daß bei ersterem das ganze Matrix-Objekt kopiert wird, während bei der Übergabe mittels Referenz (oder auch mittels Zeiger) intern nur die Adresse des Objekts übergeben wird.



  • @Th69 Maximal verwirrende Antwort...
    Um value oder reference geht es gar nicht, sondern darum, dass man eben nicht temporäre Objekte an nicht-const Referenzen binden kann.



  • @SeppJ sagte in Probleme mit dynamischen Arrays (malloc und free):

    Und da der Compiler nicht ausschließen kann, dass dein Multiplikationsoperator so etwas nicht tut, lässt er das nicht zu.

    Danke für diese sehr verständliche Erklärung! Ich persönlich finde es sehr wichtig, dass ich verstehe, warum etwas so gemacht wird, wie es gemacht wird.
    C++ ist durchaus komplex, aber mit solchen Erklärungen wird das Verständnis stetig klarer.

    Wird das Ergebnis einer temporären Rechnung als Objekt gespeichert und zeigt die Referenz darauf? Eine Referenz zeigt ja auf eine Speicheradresse.
    Ein kleiner Test mit

    	int z = 5;
    	int u = 17;
    	const int &ref = (z * u);
    
    	std::cout << &z << std::endl;
    	std::cout << &u << std::endl;
    	std::cout << &ref << std::endl;
    

    ergibt drei verschiedene Adressen. Mir persönlich sagt es, dass bei einer temporären Rechnung ein temporäres Objekt gespeichert wird. Ist das so?

    @Th69 sagte in Probleme mit dynamischen Arrays (malloc und free):

    Alle erzeugten Matrix-Instanzen sind Objekte und können referenziert werden (egal ob direkt oder als temporäre Ausdrücke).

    Also ja, danke. 🙂

    @Jockelx sagte in Probleme mit dynamischen Arrays (malloc und free):

    @Th69 Maximal verwirrende Antwort...

    Der zweite Absatz von Th69 ist mir mittlerweile sehr geläufig, so dass ich nur las, was ich schon wusste. Der erste Absatz von Th69 allerdings hilft meinem Verständnis ungemein.

    @Jockelx sagte in Probleme mit dynamischen Arrays (malloc und free):

    dass man eben nicht temporäre Objekte an nicht-const Referenzen binden kann

    Das hat SeppJ gut verständlich gemacht. Ich habe den Beitrag von Th69 als sinnvolle Ergänzung aufgefasst.
    Danke, dass du darauf achtest, dass der Lernprozess nicht durch Missverständnisse gefährdet wird. 🙂

    Wie gesagt, ich werde am Abend const-correctness recherchieren und mein Programm anpassen. Wenn ich fertig bin, werde ich dazu eine Rückmeldung abgeben.


  • Mod

    @Rav1642 sagte in Probleme mit dynamischen Arrays (malloc und free):

    Wird das Ergebnis einer temporären Rechnung als Objekt gespeichert und zeigt die Referenz darauf? Eine Referenz zeigt ja auf eine Speicheradresse.
    Ein kleiner Test mit

    	int z = 5;
    	int u = 17;
    	const int &ref = (z * u);
    
    	std::cout << &z << std::endl;
    	std::cout << &u << std::endl;
    	std::cout << &ref << std::endl;
    

    ergibt drei verschiedene Adressen. Mir persönlich sagt es, dass bei einer temporären Rechnung ein temporäres Objekt gespeichert wird. Ist das so?

    Kann so sein, muss aber nicht. Die Regeln der Sprache sagen, dass sich das Programm so verhalten muss, als ob da ein temporäres Objekt erzeugt würde. Generell darf ein Compiler aber beliebigen Code erzeugt, solange er sich so verhält wie das Programm es vorschreibt (sogenannte "as-if" Regel ). Dabei kann es sehr gut vorkommen, dass so Konzepte wie "Variablen", "Objekte", "Klassen", etc. komplett verloren gehen. Denn auf Maschinenebene gibt es diese Konzepte gar nicht, das sind alles nur Hilfsmittel, um dem menschlichen Programmierer das Nachdenken über das Programmverhalten zu erleichtern.

    Deine Ausgabe der Adressen der Objekte in deinem Beispiel gehört zum beobachtbaren Verhalten des Programms. Das darf der Compiler dann nicht mehr einfach wegoptimieren. Da die Regeln der Sprache sagen, dass du das Ergebnis an eine Referenz binden darfst und du dessen Adresse nehmen darfst, zwingst du hier den Compiler, tatsächlich irgendwie Speicherplatz für ein Zwischenergebnis deiner Rechnung vorzusehen. Wenn du die Adresse nicht ausgeben würdest, hätte er höchstwahrscheinlich anderen Code erzeugt, bei dem das Rechenergebnis nur temporär in irgendeinem (unadressierbarem) Rechenregister stehen würde.

    Insgesamt bin ich überrascht, dass du auf genau diese Variante gekommen bist, da dies eine der ganz wenigen Möglichkeiten ist, ein temporäres Objekt am Leben zu erhalten und dessen Adresse zu beziehen. Die direkte Variante cout << &(z*u); würde beispielsweise nicht funktionieren.



  • @Rav1642 sagte in Probleme mit dynamischen Arrays (malloc und free):

    Bisher habe ich mich in das Thema der Rule of Five nicht einarbeiten können, wahrscheinlich auch, weil ich für meine Zwecke keinen Bezug finde.

    Überleg' mal, was passiert, wenn Du

    struct foo
    {
        int *bar;
        foo() : bar{ new int{} } {}
        ~foo() { delete bar; }
    };
    

    machst bei

    foo f;
    foo g{ f };
    g = f;
    


  • @Rav1642 sagte in Probleme mit dynamischen Arrays (malloc und free):

    Aber schon wenn ich
    friend Matrix operator* (Matrix& m, Matrix& n);

    habe, gibt es Probleme, weil ich in vielen Situationen ja keine Objekte habe, von denen referenziert wird.

    Der operator* soll doch weder deine Matrix m noch deine Matrix n ändern. Daher ergibt "Matrix &" keinen Sinn als Parameter. Nimm stattdessen "const Matrix &". Konstante Referenzen funktionieren auch mit temporären Objekten.

    Die Frage ist aber, ob solche langen Ausdrücke dann so mit dieser Art der Entwicklung optimal sind. Denk mal darüber nach, ob jedes Zwischenergebnis sein eigenes Objekt braucht. Vielleicht ist manchmal eine inplace arbeitende Funktion besser* - oder deine Rechenoperationen liefern nicht Matrix, sondern irgendeinen in Matrix konvertierbaren Typ zurück, der automatisch bei weiteren Rechenoperationen anders/effizienter rechnet.

    * im Regelfall würde ich immer raten, den operator*= zu implementieren und dann operator* mit Hilfe von Kopie und *= zu implementieren. Analog für +, - und /.



  • Ich habe das Programm mit const-Referenzen zum Laufen gebracht.
    Auch wenn ich im Laufe des Wochenendes nochmal genauer nachlesen muss, da ich sicher noch nicht const correctness vollständig umsetzen konnte, kann ich ja mal zeigen, was ich hab:

    #pragma once
    #include <vector>
    
    class Matrix {
    private:
    	std::vector<double> values;
    	int r; // number of rows
    	int c; // number of columns
    	double lowestVal;
    	double highestVal;
    
    public:
    	// constructor
    	Matrix(const int &rows, const std::vector<double> &vals);
    
    	// special methods
    	void print(const int &precision = 1);
    	Matrix submatrix(const int &deletedRow, const int &deletedColumn);
    	Matrix transposed();
    	double determinant();
    	//Matrix adjugate();		// coming soon
    
    	// standard setter
    	void setValue(int row, int column, double newValue);
    	void setValue(int pos, double newValue);
    
    	// standard getter
    	const std::vector<double> &getValues() const;
    	const double &getValue(const int &row, const int &column) const;
    	const double &getValue(const int &pos) const;
    	const double &getLowestVal() const; // lowest value
    	const double &getHighestVal() const; // highest value
    	const int &getRows() const;
    	const int &getColumns() const;
    	const int &size() const; // rows * columns
    
    	// operator overloading
    	friend Matrix operator- (const Matrix &m); //unary
    	friend Matrix operator+ (const Matrix &m, const Matrix &n);
    	friend Matrix operator- (const Matrix &m, const Matrix &n);
    	friend Matrix operator* (const Matrix &m, const Matrix &n);
    	friend Matrix operator* (const Matrix &m, const double &d);
    	friend Matrix operator* (const double &d, const Matrix &m);
    };
    

    @SeppJ sagte in Probleme mit dynamischen Arrays (malloc und free):

    Generell darf ein Compiler aber beliebigen Code erzeugt, solange er sich so verhält wie das Programm es vorschreibt (sogenannte "as-if" Regel ).

    @SeppJ sagte in Probleme mit dynamischen Arrays (malloc und free):

    Denn auf Maschinenebene gibt es diese Konzepte gar nicht

    Das ist sehr einleuchtend! Dann brauche ich mir fürs Erste nicht allzu sehr Gedanken über temporäre Objekte machen. Letztlich scheint hinter diesen kein Geheimnis zu stecken. Tatsächlich ist dann eine konstante Referenz alles, was ich im Zusammenhang mit meinem aktuellen Projekt brauche, um mit einem temporären Objekt sinnvoll zu arbeiten.

    @Swordfish sagte in Probleme mit dynamischen Arrays (malloc und free):

    Überleg' mal, was passiert, wenn Du

    struct foo
    {
        int *bar;
        foo() : bar{ new int{} } {}
        ~foo() { delete bar; }
    };
    

    machst bei

    foo f;
    foo g{ f };
    g = f;
    

    Ehrlich gesagt sehe ich jetzt kein Problem. Ich nehme mal an, dass f.bar die Adresse von allokiertem Speicher erhält und in der folgenden Zeile erhält g denselben allokierten Speicher.
    Allerdings ist es ja grundsätzlich kein Problem, wenn zwei Strukturen über denselben Speicher verfügen. Zumindest ist es kein Problem insofern, als dass maximal semantische Fehler resultieren.
    Die Zuweisung von f zu g sollte an sich nichts ändern und ist meiner Meinung nach überflüssig.
    Wahrscheinlich bin ich voll am Ziel vorbei ... 😅

    @wob sagte in Probleme mit dynamischen Arrays (malloc und free):

    Denk mal darüber nach, ob jedes Zwischenergebnis sein eigenes Objekt braucht.

    Na, jedes Zwischenergebnis braucht ein Objekt, wenn es nach mir ginge. Aber temporäre Operationen sind bei Verwendung von Matrizen in Formeln schwer zu vermeiden, oder nicht?
    Mit const Matrix &m spare ich ja schon maximal, wenn ein Parameter notwendig ist.

    @wob sagte in Probleme mit dynamischen Arrays (malloc und free):

    im Regelfall würde ich immer raten, den operator*= zu implementieren und dann operator* mit Hilfe von Kopie und *= zu implementieren. Analog für +, - und /.

    Das werde ich beherzigen. Morgen oder übermorgen werde ich diese Operationen überarbeiten, danke für den Tipp.



  • const int &size()

    Das ist schon wieder übertrieben. Sowas solltest du einfach als int, double usw. zurückgeben.



  • @Rav1642 sagte in Probleme mit dynamischen Arrays (malloc und free):

    Wahrscheinlich bin ich voll am Ziel vorbei ...

    Ja. Was passiert denn wenn die Destruktoren laufen?

    Und wahrscheinlich möchte ich auch nicht mit einer Änderung an dem Dingsti auf das f.bar zweigt gleichzeitig das ändern, worauf g.bar zeigt.

    @Rav1642 sagte in Probleme mit dynamischen Arrays (malloc und free):

    maximal semantische Fehler

    Ach so. Na dann ist ja gut.


Anmelden zum Antworten