Probleme mit dynamischen Arrays (malloc und free)



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

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

    Bahnhof. Kompilierbares Minimalbeispiel oder das ist nie passiert.



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

    gibt es Probleme

    Aha. Welche?

    Vermutlich würde const helfen.

    friend Matrix operator* (const Matrix& m, const Matrix& n);
    


  • @Swordfish

    Da ich Matrizen nutze, möchte ich sie in mathematischen Formeln verwenden.
    Das bedeutet, dass

    ((4 * h) * -(g * h))
    

    eine Matrix ergeben soll. Ich müsste sonst für jede Teilrechnung ein Objekt erstellen und das ist schwer suboptimal.



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

    Aha. Welche?

    Der Compiler verweigert den Dienst, weil die Operationen nicht definiert seien.

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

    Vermutlich würde const helfen.

    Gerade getestet, aber bei

    Matrix operator* (const Matrix& m, const Matrix& n) { //... }
    

    lässt der Compiler diesen Aufruf nicht mehr zu:

    if (!(m.getColumns() == n.getRows())) { //...}
    

    Dann kann ich nicht mehr auf Klassenmethoden von m und n zugreifen.



  • @Rav1642 Du zeigen was Du geschrieben und erklären was für Probleme Du haben.



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

    lässt der Compiler diesen Aufruf nicht mehr zu:
    if (!(m.getColumns() == n.getRows())) { //...}

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

    Guck dir auch das Thema 'const-correctness' an. So etwas wie getSize() sollte const sein. Das ist auch etwas, was du jetzt wahrscheinlich nicht unbedingt machen musst, aber du könntest später Probleme bekommen, wenn du es nicht tust. Und wenn du dann Probleme bekommst, musst du gleich sehr viel ändern

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

    Der Compiler verweigert den Dienst,

    Aha

    Matrix.cpp C6666: Dienst verweigert

    Fehlermeldung: Copy&paste



  • @Swordfish

    #include "Matrix.h"
    #include <iostream>
    
    int main() {
    	Matrix h = Matrix(3, { 1, 1, 0, 2, -40, 5, 20, 6, -3 });
    	Matrix g = Matrix(3, { 7, 5, 0, 81, -6, 8, -10, 0, 2 });
    	((4 * h) * -(g * h)).print();
    	
    	std::cin.get();
    	return 0;
    }
    

    mit

    	friend Matrix operator* (Matrix& m, Matrix& n);
    

    in Class Matrix in matrix.h bewirkt, dass der mittlere Multiplikationsoperator * bei

    ((4 * h) * -(g * h))
    

    rot unterstrichen ist.
    Der Compiler sagt dann:
    Kein "*"-Operator stimmt mit diesen Operanten überein.
    Operandentypen sind: Matrix * Matrix



  • @Rav1642 Hst du die Antwort von @manni66 gelesen? Eine bessere Antwort wirst du nicht mehr kriegen: const!



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

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

    Der Compiler verweigert den Dienst,

    Aha

    Da stand ja die Begründung hinter, das hat ja nichts mit copy und paste zu tun.

    Ich habe in den letzten paar Tagen einiges gelernt. Dass ich die große Menge an Vorschlägen und Hinweisen bis hier hin noch nicht vollständig durchgearbeitet und verstanden habe, liegt sicher nicht an fehlendem Wille.
    Ich bemühe mich, möglichst viel von dem umzusetzen, was mir hier geraten wird. Ich setze ja nicht nur Recherchen, Codeoptimierungen und Fehlerkorrektur um, sondern schreibe ja auch die Methodenkörper. Da muss ich meine Zeit, die ich am Tag für C++ nutzen kann, sinnvoll aufteilen und habe noch nicht alles auf dem Schirm, entschuldige.


  • Mod

    Ja, das ist die fehlende Const-correctness, die dich jetzt beißt. Bei einfachen Ausdrücken wärst du vielleicht ohne weg gekommen, aber hier werden temporäre Ausdrücke erzeugt, und der Compiler hält sich selber natürlich ganz strikt an const-correctness. Das heißt, die Const-heit der temporären Ausdrücke sorgt dafür, dass sie nicht zu der Signatur deiner Multiplikationsfunktion passen, die (fälschlich) zwei nicht-const werte erwartet.

    Nun ist der Zeitpunkt gekommen, vor dem ich gewarnt habe, an dem du sehr viel ändern musst. Denn so ein const ist ansteckend. Wenn du die Referenzen zu dem Multiplikationsoperator nun korrekt const machst, wird es gewiss sofort oder bald an anderen Stellen zu Problemen kommen, wo du auch const fälschlich weggelassen hast. Letztlich wirst du alles const-correct machen müssen. Solltest du aber sowieso. Const ist schließlich nicht dazu da, um dich zu ärgern, sondern ein Hilfsmittel, das dir hilft, Fehler zu finden.



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

    Hst du die Antwort von @manni66 gelesen? Eine bessere Antwort wirst du nicht mehr kriegen: const!

    Klar, ich halte mich stets auf dem Laufenden! 🙂
    Ich sehe, dass da mehr zu tun ist und kann das erst heute Abend umsetzen.

    Aber warum ist const hier die Lösung?
    Als Argument erwarte ich ein Objekt, auf das ich referenzieren kann, aber bei

    ((4 * h) * -(g * h))
    

    sind g und h zwar Objekte, allerdings die Matrizen nicht, die im letzten Schritt multipliziert werden. Also übergebe ich der Funktion ja kein Objekt. Also kann ja auf nichts referenziert werden, oder nicht? Warum kann ich mit const dann auf etwas referenzieren, das man doch eigentlich nicht referenzieren kann?
    Das entzieht sich aktuell leider meinem Verständnis.

    Ich danke für die Antwort!


  • 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.


Anmelden zum Antworten