Probleme mit dynamischen Arrays (malloc und free)



  • Hallo,

    ich möchte ein Programm schreiben, mit dem ich grundlegende Matrix-Operationen anstellen kann. Ganz am Anfang steht jedoch die Klasse 'Matrix', die ich in einer matrix.h stehen hab. Ich möchte also die Matrix selbst als Typ nutzen und später innerhalb der Klasse die Operationen programmieren. Bisher möchte ich allerdings erstmal das Grundgerüst schaffen, bestehend aus doppeltem Pointer auf int, worin die Werte gespeichert werden mit Verwendung von malloc (ich weiß, dass es Alternativen gibt, allerdings möchte ich an diesem Fehler lernen statt das Problem zu umgehen, ohne die Lösung zu kennen).
    Zu matrix.h existiert nun eine matrix.cpp, in der die Klassenmethoden definiert sind.
    Im Konstruktor rufe ich die Methode allocate() auf, die folgendes tut:

    int Matrix::allocate() {
    	// we have to allocate space for the double pointer 'values'
    	values = (int **)malloc(r *sizeof(int **));
    	if (values == NULL) {
    		return -1;
    	}
    	// the inner pointer of each outer pointer needs to get an allocation, too
    	for (int i = 0; i < r; i++) {
    		values[r] = (int *)malloc(c * sizeof(int *));
    		if (values[r] == NULL) {
    			return -100 - r;
    		}
    	}
    	return 0;
    }
    

    mit

    int **values; 
    

    deklariert in matrix.h.
    Nun wirft der Zugriff (lesen/schreiben) auf Elemente von values einen Zugriffsfehler.
    Beispielsweise der Destruktor:

    Matrix::~Matrix() {
    	// set the allocated space of 'values' free, in reverse order
    	for (int i = 0; i < c; i++) {
    		free(values[i]);
    	}
    	free(values);
    }
    

    c steht für columns, also die Anzahl der Spalten der Matrix. (c an der Stelle ist schon richtig; es hätte auch r für rows sein können, aber die c sind den r untergeordnet; um Fehler zu erkennen, habe ich den Fall c = r natürlich getestet, ohne dass der Fehler weg ging)
    Beim Aufruf von free(...) meldet Visual Studio 2017:
    Ausnahme ausgelöst bei 0x00007FFA00D3197C (ucrtbased.dll) in Collection.exe: 0xC0000005: Zugriffsverletzung beim Lesen an Position 0xFFFFFFFFFFFFFFFF.

    Ich nutze Win 10 64 bit und habe das Projekt auf x64 eingestellt.
    Der gesamten bisherige Code inkl. Kommentare ist von mir geschrieben, demnach sind auch alle Fehler von mir.
    Seit einigen Abenden versuche ich per Foren und Büchern zu lösen und erkenne einfach kein Problem. Deswegen freue ich mich sehr über Hilfe. Wie kann ich diesen Fehler vermeiden und das Programm zum Laufen bringen?

    Vielen Dank!

    MfG Pascal


  • Mod

    Da lernst du falsch.

    malloc/free sind C, nicht C++. Außerdem verwaltet man in C++ Ressourcen ganz grundlegend anders. Selbst wenn du es selber schreiben willst (was du nicht nötig hast), bist du derzeit total auf dem Holzweg. Google mal 'RAII'.

    Ein Zeiger ist keine Array und ein Array ist kein Zeiger, entsprechend hat ein Zeiger auf einen Zeiger noch viel weniger mit einer 2D-Matrix zu tun. Das ist eher eine Liste von Listen. Eine Matrix stellt man stattdessen mit einer eindimensionalen Struktur plus Zugriffslogik dar.

    Halte auch Funktionalität unbedingt sauber getrennt. Hast du schon einmal in einem Mathebuch gelesen, dass eine Matrix Speicher verwalten würde? Sicher nicht. Die Aufgabe 'Speicherverwaltung' ist völlig losgelöst von 'Matrixrechenoperationen' und sollte auch dementsprechend programmiert werden.

    Das sind erst einmal drei derart fundamental Designprobleme an deinem Programm, dass man eigentlich nicht sinnvoll weiter helfen kann, bevor diese nicht abgestellt wurden. Diese Probleme sind übrigens auch ziemlich direkt für deinen derzeitigen Fehler verantwortlich. Löse die Probleme und der Fehler wird von alleine verschwinden. Man könnte natürlich auch irgendwie deinen unmittelbaren Fehler weghacken, ohne dein Design zu ändern, aber das wäre nur Vorbereitung des nächsten Fehlers.



  • @SeppJ
    Danke für die schnelle Antwort. Es haben sich direkt Fragen aufgeworfen, die mein Problem betreffen.
    Zu den ersten beiden Punkten: In der Uni (Modul Betriebssysteme) hatte ich mal eine Speicherverwaltung mit malloc und free über Zeiger programmiert. Das war C, jedoch hatte ich kürzlich gelesen, dass malloc und free auch in C++ problemlos funktionieren.
    Ein kleines Testprogramm:

    int **k;
    	k = (int **)malloc(2 * sizeof(int **));
    	if (k != NULL) {
    		k[0] = (int *)malloc(2 * sizeof(int *));
    		k[1] = (int *)malloc(2 * sizeof(int *));
    		if (k[0] != NULL && k[1] != NULL) {
    			k[0][0] = 4;
    			k[0][1] = 17;
    			k[1][0] = 22;
    			k[1][1] = 101;
    
    			std::cout << k[0][0] << " " << k[0][1] << " "
    				<< k[1][0] << " " << k[1][1] << std::endl;
    			free(k[0]);
    			free(k[1]);
    			free(k);
    		}
    		else {
    			std::cout << "ERROR!!!" << std::endl;
    		}
    	}
    	else {
    		std::cout << "ERROR!!!" << std::endl;
    	}
    

    hatte funktioniert. Leider weiß ich nicht, warum ich dann im Matrixprogramm einen Zugriffsfehler bekam.
    Dass das in C++ eigentlich nicht so gemacht wird, ist mir dann auch aufgefallen. Ich möchte es auch abändern, RAII umsetzen. Sicher wird der Fehler verschwinden, da glaube ich dir. Trotzdem fühlt es sich störend an, dass etwas, das grundsätzlich möglich ist, bei mir Fehler wirft. Gerne verwerfe ich das Bisherige, aber beantworte mir bitte die Frage, was ich im Testprogramm mit k ** besser gemacht habe als im Matrixprogramm. Danke!

    Punkt drei verstehe ich nicht. Natürlich existiert in der Mathematik eine Trennung.
    Allerdings wurde in der Uni in den verschiedensten Modulen in solchen Situationen keine Trennung vorgenommen. Seien m1 und m2 Matritzen, dann ist m1.getDeterminant() eine hübsche Lösung. m1.multiplication(m2) wäre unschön, aber man kann innerhalb der Klasse ja die Operatoren berücksichtigen, so dass m1 * m2 eine Matrix ergibt.
    Ich wüsste gar nicht, wie ich das möglich machen könnte, wenn sowas nicht direkt in der Klasse stehen würde. Kannst du mir dazu was sagen? Auch hierfür nochmal danke!

    Grüße Pascal



  • Okay, ich habe meinen Fehler gefunden.

    int Matrix::allocate() {
    	// we have to allocate space for the double pointer 'values'
    	values = (int **)malloc(r * sizeof(int **));
    	if (values == NULL) {
    		return -1;
    	}
    	// the inner pointer of each outer pointer needs to get an allocation, too
    	for (int i = 0; i < r; i++) {
    		values[r] = (int *)malloc(c * sizeof(int *)); // <<<--- hier muss i statt r
    		if (values[r] == NULL) { // <<<--- hier auch
    			return -100 - r;
    		}
    	}
    	return 0;
    }
    

    Der Fehler, weswegen ich hier bin, ist gelöst (typisch dummer Fehler).
    Allerdings möchte ich noch gerne das zuletzt angesprochene klären, damit ich mich verbessern kann.
    Und zwar zur Trennung der Funktionalität (wieso die Operationen nicht der Matrix direkt beiliegen sollte; wie es ansonsten gelöst werden sollte).
    Grüße
    Pascal



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

    jedoch hatte ich kürzlich gelesen, dass malloc und free auch in C++ problemlos funktionieren.

    ja vor 30 jahren oder so wollte man durch diese kompatibilität zu C den umstieg erleichtern. aber das funktioniert nicht immer bzw. führt zu merkwürdigen fehlern, und deshalb sollte man das lassen.

    k = (int **)malloc(2 * sizeof(int **));

    klausurfrage: finden sie den fehler in dieser anweisung!



  • @Wade1234
    Mit

    k = (int **)malloc(2 * sizeof(int **));
    

    hatte ich keine Probleme, das funktioniert ohne Fehler.
    Malloc gibt ja einen void-Zeiger zurück. Den muss man dann explizit casten. 2*sizeof(...), weil ich in diesem Mini-Programm eine feste 2x2-Matrix hatte.
    Ich weiß daher nicht, worauf du hinaus willst.

    Grüße
    Pascal



  • @Rav1642
    naja worauf zeigt k nach dem aufruf von malloc?



  • @Wade1234
    Ich nehme an, dass k dann auf die Speicheradresse zeigt, ab der der reservierte Speicherplatz mit Größe 2 * sizeof(int **) beginnt. Nicht?



  • Du solltest Dir das von SeppJ geschriebene zu Herzen nehmen... Du willst doch sauber und solide programmieren lernen und nicht irgendwas zusammenpfuschen.

    Um zu deinem Code zu kommen:

    values[r] = (int *)malloc(c * sizeof(int *));
    

    In dieser Zeile sind 4 grobe Fehler auf einmal.

    • Verwendung von malloc in C++
    • Verwendung von sizeof(int*), obwohl Du vermutl. sizeof(int) meinst
    • Verwendung von r als Index anstatt i --> off-by-one (indices gehen on 0-(r-1))
    • Verwendung von r als Index anstatt i --> Du überschreibst (r-1) allokierte Speicherbereiche und damit ist es ein Leak (weil Du den Speicher nie wieder freigeben kannst)
    • (int*) ist ein C-style cast und sollte in C++ ebenfalls nicht verwendet werden.
    for (int i = 0; i < c; i++)
    

    Das stimmt auch nicht, Du hast ja r Zeilen und nicht c Zeilen. Und da values[0] nicht initialisiert ist (allocate beschreibt nur das Element nach dem letzten Element in values), ist ein free auch nicht gültig, und damit eine Zugriffsverletzung.



  • @Rav1642
    ja das ist richtig. int **k ein zeiger auf was für einen datentyp? wenn int **k nach dem aufruf von malloc auf einen speicherbereich von n dieser elemente zeigen soll, welche speichermenge muss dann angefordert werden?



  • @HarteWare
    Danke für deine Antwort!

    Sehr gerne nehme ich mir das von ihm Geschriebene zum Herzen.
    Das habe ich ja bereits geschrieben, jedoch haben sich für mich Fragen aufgeworfen. Die Trennung von Funktionalität wie es SeppJ schrieb ist für mich nach wie vor nicht klar, weswegen ich ja nochmal nachgefragt habe.

    Zu Zeile :

    values[r] = (int *)malloc(c * sizeof(int *));
    

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

    • Verwendung von malloc in C++
    • Verwendung von sizeof(int*), obwohl Du vermutl. sizeof(int) meinst
    • Verwendung von r als Index anstatt i --> off-by-one (indices gehen on 0-(r-1))
    • Verwendung von r als Index anstatt i --> Du überschreibst (r-1) allokierte Speicherbereiche und damit ist es ein Leak (weil Du den Speicher nie wieder freigeben kannst)
    • (int*) ist ein C-style cast und sollte in C++ ebenfalls nicht verwendet werden.
    • dass ich malloc in C++ verwendet habe, war sicherlich kein guter Stil, das habe ich nun gelernt
    • das mit sizeof() war mir komplett entgangen, danke
    • mein ursprünglicher Fehler beruhte hierauf, das habe ich dann gelöst
    • das gilt auch hierfür
    • den Punkt verstehe ich nicht; ist das problematisch, weil es ein Pointer ist? Muss man gar keinen Pointer casten und kann ich das ersatzlos weglassen?

    Zu:

    for (int i = 0; i < c; i++)
    

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

    Das stimmt auch nicht, Du hast ja r Zeilen und nicht c Zeilen. Und da values[0] nicht initialisiert ist (allocate beschreibt nur das Element nach dem letzten Element in values), ist ein free auch nicht gültig, und damit eine Zugriffsverletzung.

    Das war mir dann auch aufgefallen. Aber danke, dass du mich darauf nochmal hingewiesen hast! 🙂

    @Wade1234
    Danke, dass du die Geduld aufbringst, denn jetzt glaub ich, dass ich dir folgen kann. Der entscheidende Tipp kam ja bei HarteWare zur Sprache.
    Es muss wahrscheinlich heißen:

    k = malloc(2 * sizeof(int *));
    

    Richtig?



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

    Punkt drei verstehe ich nicht. Natürlich existiert in der Mathematik eine Trennung.
    Allerdings ...

    Das ist nicht das, worauf SeppJ hinaus wollte. Er hat ja auch explizit von "Speicherverwaltung" und "Matrixrechenoperationen" geschrieben.
    Zur Speicherverwaltung gehört ja ein bisschen was dazu. Wenn du das manuell machst und das korrekt machen wolltest, noch mehr als das was du bisher gemacht hast. Das hat jetzt aber alles nichts mit der Klasse Matrix zu tun. Eine andere Klasse, die etwas ganz anderes macht, könnte evtl. genau die gleiche Logik zur Speicherverwaltung brauchen. Deswegen macht es keinen Sinn, so etwas kompliziertes und allgemeines, in einer konkreten Klasse wie Matrix zu implementieren.
    Und das nächste ist, vielleicht willst du das ja später anders machen. Wenn deine Matrix intern malloc oder new[] macht, hast du keine Chance, das von außen zu ändern. Stell dir aber vor, dass du mal einen Pool Allokator verwenden willst...



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

    @HarteWare
    Danke für deine Antwort!

    Sehr gerne nehme ich mir das von ihm Geschriebene zum Herzen.
    Das habe ich ja bereits geschrieben, jedoch haben sich für mich Fragen aufgeworfen. Die Trennung von Funktionalität wie es SeppJ schrieb ist für mich nach wie vor nicht klar, weswegen ich ja nochmal nachgefragt habe.

    Zu Zeile :

    values[r] = (int *)malloc(c * sizeof(int *));
    

    Es kommt sehr selten vor, dass man in Klassen doch einmal eine C API nutzen muss. Aber das ist kein Anfängerthema. In der Regel sollte Speicher eine Containerklasse z.B. vector oder array genutzt werden. Geht das nicht und man muss doch Speicher anfordern, sollte man Smart Pointer nutzen. Unbedingt unique_ptr, shared_ptr und weak_ptr erlernen und wann man sie jeweils einsetzt. In diesem konkreten Fall, ist für einen Anfänger vector die sinnvollste Lösung. Falls es doch einmal Speicheranforderung via C API sein muss, sollte man das bitte so schreiben.

    int* p = static_cast<int*>(malloc(n * sizeof(int)));
    

    C Casts werden in C++ niemals verwendet. Dafür gibt es keinerlei Notwendigkeit.
    Aber wenn man schon manuell in C++ Speicher anfordert. Warum dann nicht die C++ Variante davon?

    int* p = new int[n];
    

    P.S. Manuelle Speicherverwaltung sollte man als Anfänger wirklich nur dann machen, wenn man es explizit lernen will. Bitte die Rule of Zero/Three/Five beachten und dann in Code umsetzen.

    Update: Klammern bei static_cast ergänzt.



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

    Und das nächste ist, vielleicht willst du das ja später anders machen. Wenn deine Matrix intern malloc oder new[] macht, hast du keine Chance, das von außen zu ändern. Stell dir aber vor, dass du mal einen Pool Allokator verwenden willst...

    Du willst einem Anfänger die korrekte Nutzung von allocator_traits erklären? Das wird spannend.



  • Ich wollte nur auf den Grund hinaus, warum es erwünscht ist, solche Funktionalität zu trennen.
    Schließt natürlich nicht aus, dass sich daraus weitere Fragen ergeben.



  • @john-0 sagte in Probleme mit dynamischen Arrays (malloc und free):

    C Casts werden in C++ niemals verwendet. Dafür gibt es keinerlei Notwendigkeit.

    Genau!

    Und wenn @Rav1642 es sehr ausführlich erklärt/begründet haben will, hätte ich zu Casts ein Video von STL anzubieten: https://www.youtube.com/watch?v=6wdCqNlnMjo&t=393 Fängt bei 6 1/2 Minuten an, über Casts zu sprechen. Ab Minute 23 folgt dann die Begründung, warum C-Style Casts schlecht sind.


  • Mod

    Man kann's aber auch in ganz wenigen Sätzen zusammenfassen:

    1. Schlechtere Lesbarkeit, wo ein Cast geschieht, da es den gleichen Aufbau wie andere oft vorkommende Syntaxelemente hat. Man muss den Komplettausdruck im Kopf parsen, um den Cast sicher zu erkennen und kann auch nicht querlesen, um Kandidaten zu filtern.
    2. Wundertüte, was genau man bekommt, da jeder Cast potentiell eine Kombination von allen C++-Casts ist. Man muss den Ausdruck nicht nur komplett parsen, sondern auch im Kopf (korrekt!) Ableiten, was an welcher Stelle welchen Typ hat und welche Castregeln gelten, um sichere Aussagen zu tätigen.

    Ist bei einem Trivialausdruck wie (double) int_var vielleicht nicht ganz so schwerwiegend, aber man braucht keine unrealistischen Beispiele heranziehen, bevor die Nachteile relevant werden.



  • @SeppJ Das einzige was mit C-Style nicht geht ist dynamic_cast<> oder?



  • ...und wenn man mit Zeigern auf Objekte sowie Objekte abgeleiteter Klasse herumhantiert. U.u. muss dann ein Offset addiert werden, was bei einem C-Cast nicht passiert (aber z.B. mit static_cast).



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

    @SeppJ Das einzige was mit C-Style nicht geht ist dynamic_cast<> oder?

    Doch, auch das geht.


Anmelden zum Antworten