Wo "delete[]" setzten, wenn Array mit return aus Funktion übergeben werden soll + Exception notwendig?



  • Hey,

    ich weiß ich komme mit einer oft gestellten Frage, ich habe aber im Netz nirgends eine befriedigende Antwort finden können.

    1. Es geht darum, dass ich ein Array in einer Funktion benutze und zur Übergabe mit new [] einen Pointer deklariere, da ich ja ein Array als solches nicht übergeben kann. Mein ganzen Programm läuft im Moment auch recht gut, allerdings ist es noch nicht sauber geschrieben, da mir noch die dazugehörige delete[] Funktion fehlt. Ich weiß aber nicht wo ich die ansetzen soll, in der Funktion selber geht ja eigentlich nicht, schließlich will ich den Wertmit return ja noch zurückgeben. Wenn ich das aber versuche im main zu lösen erhalte ich (logischerweise) einen Fehler, dass meine entsprechende Variable nicht deklariert wurde (sie ist ja schließlich nur der Funktion bekannt). Wie löse ich das Problem?

    Im Netz wurde immer wieder auf: std::vector verwiesen, allerdings kenne ich mich mit dieser Containerklasse (stimmt die Bezeichnung?) noch nicht wirklich aus, versuche mich da aber mal reinzulesen. Mich interessiert vor allem, ob auch ohne die Benutzung von vector das sauber zu lösen ist.

    2. Habe ich auch in Tutorials gefunden, dass man zum besseren Handling für solche Speicherprobleme exceptions verwendet. Ich habe versucht so ein Beispiel mal nachzuahmen und in den Codeteil unten try und catch eingebaut, worauf das Programm nicht mehr funktionierte, weil es dann rot[0] usw. nicht mehr erkannte. Muss das jetzt in den try - Teil mit rein, wenn ja warum und macht das überhaupt Sinn? (Habe im main: #include <exception> drinstehen.)

    (Zum Verständnis, der Code ist eine gekürzte Funktion, die 3 Winkel und einen Vektor einliest, der Vektor wird dann mit Hilfe einer Rotationsmatrix gedreht, der neue, gedrehte Vektor soll zurückgegeben werden.)

    double *transrotation (double ar, double br, double cr, double *vector)
    {
    
           //hier kommen einige für die Frage unwichtige Berechnungen
    
           //preparing new vector array
           try
           {
              double *rot = new double[3];   //pointer to vector "rot" (X,Y,Z)
           }
           catch (bad_alloc&)
           {
              cout << "Error allocating memory." << endl;   
           }
    
           rot[0] = rot_1;   // X
           rot[1] = rot_2;   // Y
           rot[2] = rot_3;   // Z
    
           return rot;
    }
    

    Danke für eure Mühen mit immer wieder denselben Fragen... 🙂
    Fruchti



  • du rufst das ja so auf:

    double * ptr;
    ptr = transrotation(...);
    

    dann kannst du einfach ptr per delete[] löschen

    delete[] ptr;
    


  • Fruchti schrieb:

    double *transrotation (double ar, double br, double cr, double *vector)
    {
           //...
           try
           {
              // Variable nicht lokal im try-Block deklarieren, erst die Zuweisung
              // mit new sollte hier stehen.
              double *rot = new double[3];   //pointer to vector "rot" (X,Y,Z)
           }
           catch (bad_alloc&)
           {
              // So etwas bringt _garnichts_:
              cout << "Error allocating memory." << endl;
              // Wenn man bad_alloc nicht sinnvoll behandeln kann, sollte die
              // Exception auch nicht abgefangen werden. In deinen Fall:
              // Würde die Exception geworfen werden, wäre der anschließende
              // Zugriff vermutlich eine "Access Violation", bzw. undefiniert -
              // das hilft nicht wirklich.
           }
           //...
    }
    

    Imho besser:
    a) Diese Exception nicht abfangen (oder erst "ganz" oben, um dort ein Trace etc. zu machen).
    b) Zeiger mit new allokieren, und anderswo freizugeben ist meiner Meinung nach schlechter Programmierstil. Besser: entweder auf Objekte zur Datenhaltung umschwenken (z.B. std::vector, std::tr1::array) oder mit Smartpointern (z.B. boost::shared_array) arbeiten.

    cu André



  • Sprich, auf meinen Beitrag bezogen, ich lösche nicht (wie von mir immer geglaubt):

    delete[] rot;
    

    irgendwo, sondern schreibe:

    delete[] transrotation;
    

    und damit habe ich automatisch das Problem mit rot geklärt?



  • Fruchti schrieb:

    delete[] transrotation;
    

    Nur wenn dein Zeiger dem du das Ergebnis von deiner Funktion zuweist ebenso wie deine Funktion transrotation heißt. Ich gehe aber eher davon aus das du hier ein Missverständlich hast und dich nochmal mit Zeigerarithmetik näher auseinander setzen solltest...



  • Nein, das hab ich schon verstanden, mich aber zugegebenermaßen sehr schlecht ausgedrückt, natürlich muss ich den entsprechenden Zeiger zu der Funktion (bei mir trans) mit delete verbinden. Sry, dass ich das falsch geschrieben habe.

    Ich werde die Variante erstmal so anwenden, damit das Programm ungefährlich funktioniert und aber trotzdem versuchen mich zu deinen Vorschlägen nochmal einzulesen und das entsprechend zu modifizieren, vielen Dank André. Und Sky0m0sh für die Variante überhaupt.

    Die Exception werd ich dann wohl auch erstmal weglassen. Wenn es auch locker ohne geht, ohne dass Probleme auftauchen, kann ich gut darauf verzichten.



  • Fruchti schrieb:

    Die Exception werd ich dann wohl auch erstmal weglassen. Wenn es auch locker ohne geht, ohne dass Probleme auftauchen, kann ich gut darauf verzichten.

    Sagen wir es so:
    Die Exception kann dir natürlich Probleme bereiten. Aber: Eine Exception sollte man auch nur da fangen, wo man sie auch sinnvoll behandeln kann. Dies kannst du aber in deiner Funktion nicht (Der Speicher ist nicht verfügbar, und die Zugriffe danach machen keinen Sinn wenn man keinen Speicher hat).

    Diese Exception könntest du maximal um den Aufruf und die anschließende Behandlung und Freigabe packen (Sprich: Im Falle der Exception wird die Funktion direkt verlassen und die Behandlung und Freigabe - du hast ja ohnehin keinen gültigen Speicherbereich - übersprungen).

    Zudem gehört bad_alloc zu den Exceptions bei denen man in der Regel ohnehin nicht viel machen kann. Die einzige Strategie die ich kenne ist die, das man am Programmstart einen Speicherbereich alloziert, den man im Falle eines bad_alloc freigibt und dann einen Hinweis das zu wenig Speicher vorhanden ist anzeigt. Bedingt durch den freigegebenen Speicherbereich reicht es vielleicht noch zum Speicher o.ä.

    Eine Exceptionbehandlung erreicht man nicht in dem man einfach ein try/catch hinsetzt. Sondern wenn try/catch: Bitte an den Stellen wo du etwas machen kannst, damit das Programm anschließend geordnet weiterlaufen kann.

    cu André



  • asc schrieb:

    Zudem gehört bad_alloc zu den Exceptions bei denen man in der Regel ohnehin nicht viel machen kann.

    Warum?
    Wenn man durch Nutzereingaben ausgelöst einen viel zu großen Speicherblock allozieren muß, dann heißt das doch nicht unbedingt, daß kein Speicher mehr frei ist. Beispiel Bildverarbeitung: Nutzer gibt viel zu große Bildgröße ein und der Speicher reicht nicht aus. Natürlich kann man in so einem Fall sinnvoll weitermachen, in dem man dem Nutzer die Rückmeldung gibt, daß der Speicher für seine Wunschbildgröße nicht ausreichend ist.



  • ~john schrieb:

    asc schrieb:

    Zudem gehört bad_alloc zu den Exceptions bei denen man in der Regel ohnehin nicht viel machen kann.

    Warum?
    Wenn man durch Nutzereingaben ausgelöst einen viel zu großen Speicherblock allozieren muß, dann heißt das doch nicht unbedingt, daß kein Speicher mehr frei ist. Beispiel Bildverarbeitung: Nutzer gibt viel zu große Bildgröße ein und der Speicher reicht nicht aus. Natürlich kann man in so einem Fall sinnvoll weitermachen, in dem man dem Nutzer die Rückmeldung gibt, daß der Speicher für seine Wunschbildgröße nicht ausreichend ist.

    Da wird dann aber nie bad_alloc ausgelöst, weil du ja (wie man das am besten macht) vorher schaut, ob es überhaupt genug Speicher hat. Wenn aber mal bad_alloc ausgelöst werden sollte, dann kannst du da tatsächlich nicht mehr viel machen. Wie auch ohne Speicher..



  • drakon schrieb:

    Da wird dann aber nie bad_alloc ausgelöst, weil du ja (wie man das am besten macht) vorher schaut, ob es überhaupt genug Speicher hat. Wenn aber mal bad_alloc ausgelöst werden sollte, dann kannst du da tatsächlich nicht mehr viel machen. Wie auch ohne Speicher..

    Wenn eigentlich noch genügend Speicher vorhanden wäre, aber halt kein ausreichend grosser Block, wird doch auch std::bad_alloc geworfen, oder?

    Wie will man überhaupt vorher schauen, ob noch genügend Speicher verfügbar ist?

    Und falls bad_alloc ausgelöst würde, wäre wahrscheinlich wieder ein wenig Speicher frei vom Stack, durch den die Exception fliegt. Das könnte eventuell reichen, um Heap-Speicherplatz wieder freizugeben.



  • Nexus schrieb:

    drakon schrieb:

    Da wird dann aber nie bad_alloc ausgelöst, weil du ja (wie man das am besten macht) vorher schaut, ob es überhaupt genug Speicher hat. Wenn aber mal bad_alloc ausgelöst werden sollte, dann kannst du da tatsächlich nicht mehr viel machen. Wie auch ohne Speicher..

    Wenn eigentlich noch genügend Speicher vorhanden wäre, aber halt kein ausreichend grosser Block, wird doch auch std::bad_alloc geworfen, oder?

    Wie will man überhaupt vorher schauen, ob noch genügend Speicher verfügbar ist?

    Und falls bad_alloc ausgelöst würde, wäre wahrscheinlich wieder ein wenig Speicher frei vom Stack, durch den die Exception fliegt. Das könnte eventuell reichen, um Heap-Speicherplatz wieder freizugeben.

    Würde mal sagen, dass das alles Implementierungsspezifisch ist.
    Der Standard schreibt nur vor, dass ein Fehler bei der Allozierung des Speicher stattgefunden hat. Warum genau das passiert ist kann sehr verschieden sein.
    Auch das mit dem Block muss nicht unbedingt sein. Ich nehme mal an, dass du darauf anspielst, dass du z.B ein grosses Array haben willst,dass wegen Fragmentierung nicht an einem Stück alloziert werden kann. Aber ansonsten genügendn Speicher da wäre. Nun, da könnte das OS ja einiges machen, dass dein Programm da dennoch den Speicher bekommt. Oder (glaube hatten wir sogar schon mal) sogar, dass du ein so exotisches Betriebssystem hast, dass dir den Speicher trotz Fragmentierung beschaffen kann (ohne auräumen zu müssen).

    Vorher schauen würde ich auch mal mit OS-Spezifischen Funktionen. Wenn du das wirklich brauchst..



  • drakon schrieb:

    Da wird dann aber nie bad_alloc ausgelöst, weil du ja (wie man das am besten macht) vorher schaut, ob es überhaupt genug Speicher hat.

    Vorher nachsehen bringt nichts, es gibt keinerlei Garantie, daß das anschließend klappt. So nebenbei, was willst Du da vorher alles nachsehen?
    Z.B. auf einem UNIX kann man den Speicher für Prozesse eingrenzen, so daß zwar massig Speicher im System frei ist, aber man diesen gar nicht allozieren kann. Richtig toll ist das mit parallel laufenden Prozessen, die man nicht unter Kontrolle hat. Wie soll man deren Laufzeitverhalten vorhersagen können?



  • Fruchti schrieb:

    ...

    Ich möchte auch nochmal betonen, dass Du hier versuchst, die Konsequenzen einer schlechten Entscheidung auszubügeln: Solche "Erzeugerfunktionen" verursachen eben genau diese Probleme, weswegen man sie möglichst meiden sollte.
    Ich habe immer noch nicht begriffen, warum Du meinst, sowas zu brauchen... und warum Du keinen Std-Container (vector&Co) verwenden willst: Das Teil kannst Du genauso verwenden wie ein Array und Du bist alle diese Probleme los...

    Gruß,

    Simon2.



  • Simon2 schrieb:

    Fruchti schrieb:

    ...

    Ich möchte auch nochmal betonen, dass Du hier versuchst, die Konsequenzen einer schlechten Entscheidung auszubügeln: Solche "Erzeugerfunktionen" verursachen eben genau diese Probleme, weswegen man sie möglichst meiden sollte.
    Ich habe immer noch nicht begriffen, warum Du meinst, sowas zu brauchen... und warum Du keinen Std-Container (vector&Co) verwenden willst: Das Teil kannst Du genauso verwenden wie ein Array und Du bist alle diese Probleme los...

    Gruß,

    Simon2.

    Es ist ja nicht so, dass ich den Std-Container nicht verwenden will, sondern es verhält sich eher so, dass ich bis dato noch nicht weiß, wie man diesen Container benutzt. Ich versuche mich in diesen Sachverhalt, der neu für mich ist, einfach noch reinzufinden und habe bisher eben versucht das Programm im Rahmen meiner Möglichkeiten und Kenntnisse aufzubauen. 🙂
    (Und war froh, dass es so funktionierte.)

    Aber ich versuche es noch umzubauen und damit zu verbessern.

    Und bei den Exceptions war es ähnlich, ich habe mich einfach versucht im Internet durch viele Beispiele und Tutorials zu lesen, bin bei den Exceptions vorbeigekommen und dachte: prima, probierst du einfach mal aus...
    es ist ja schließlich doch immer so: durch Fehler lernt man. 🙄



  • Es ist ja nicht so, dass ich den Std-Container nicht verwenden will, sondern es verhält sich eher so, dass ich bis dato noch nicht weiß, wie man diesen Container benutzt.

    Die Standardcontainer sind bei weitem einfacher zu benutzen, als irgendwelches Array gefrikel. 😉 Sobald du dich ein wenig mit der Syntax angefreundet hast und weisst, was templates sind ist es eine enorme Erleichterung. Dazu kommt noch, dass, wenn du einen Container kennst praktisch alle anderen auch benutzen kannst. (gibt immer ein paar spezielle Sachen, aber das ist auch trivial).



  • Nur um mal einige Vorteile der STL-Container zu nennen:

    • Bequemes Einfügen und Löschen von Elementen.
    • Einfaches Durchiterieren und Ermitteln der Grösse möglich.
    • Speicherverwaltung wird einem abgenommen; Elemente werden automatisch zerstört, kopiert und zugewiesen, wenn benötigt.
    • Man hat ein Objekt vorliegen, während rohe Arrays oft wie Zeiger gehandhabt werden (z.B. Übergabe an Funktionen).
    • Je nach Anwendung sind andere Datenstrukturen (verkettete Liste, Binärbaum) besser geeignet als lineare Arrays.

    Als Einführung in die Standard Template Library gibt es hier im Forum gerade drei empfehlenswerte Artikel:
    1) Container
    2) Iteratoren und Algorithmen
    3) Hilfsklassen und Erweiterungen



  • Nexus schrieb:

    Nur um mal einige Vorteile der STL-Container zu nennen:

    • Bequemes Einfügen und Löschen von Elementen.
    • Einfaches Durchiterieren und Ermitteln der Grösse möglich.
    • Speicherverwaltung wird einem abgenommen; Elemente werden automatisch zerstört, kopiert und zugewiesen, wenn benötigt.
    • Man hat ein Objekt vorliegen, während rohe Arrays oft wie Zeiger gehandhabt werden (z.B. Übergabe an Funktionen).
    • Je nach Anwendung sind andere Datenstrukturen (verkettete Liste, Binärbaum) besser geeignet als lineare Arrays.

    ...

    • Einige Implementierungen bieten "range checking" - zumindestens an/abschaltbar - an.

    ... denn die Anlage von Arrays ist üblicherweise nur der erste Schritt - danach kommen üblicherweise die Threads "Warum schmiert mein Programm ab?". 😃

    Gruß,

    Simon2.



  • Simon2 schrieb:

    • Einige Implementierungen bieten "range checking" - zumindestens an/abschaltbar - an.

    Das ist sogar bei allen Implementierungen gegeben - wenn Du die Methode at() benutzt. 😃



  • Oder man debuggt halt wie jeder normale Mensch und stösst auf Assertions. 😉



  • Nexus schrieb:

    Oder man debuggt halt wie jeder normale Mensch und stösst auf Assertions. 😉

    😕
    Den Hinweis bekomme ich nicht mehr in den Diskussionszusammenhang: Ist dsas jetzt ein Argument für/gegen die Verwendung von STL-Containern oder at() oder operator[] oder ... ?
    Oder musst das einfach mal gesagt werden ? 😉

    Gruß,

    Simon2.


Anmelden zum Antworten