Ein char-Array freigeben...



  • Das muss so ziemlich der blödeste Fehler sein, den ich je hatte.

    Ich habe ein zweidimensionales, dynamisch allokiertes char-Array, also ein Array aus C-Style-Strings, char **values. Jetzt möchte ich einen davon überschreiben. So wie ich das verstehe, muss ich jetzt aber zuvor mittels delete den vorherigen Inhalt dieses Arrays löschen, um kein Speicherleck zu erzeugen. Der Code, den ich verwende:

    delete[] values[0];
    values[0] = buffer;
    

    Wobei es hier zu einer fehlgeschlagenen Assertion in der Zeile mit dem delete kommt. (Expression: _CrtIsValidHeapPointer(pUserData)). Aber seit ich mich erinnern kann, gibt man so ein char-Array frei. Entweder habe ich also gerade eine extremen Blackout, oder mir ist woanders ein dummer Fehler unterlaufen.

    Der Konstruktor für die Klasse, in der das stattfindet, sieht so aus:

    ControlInfo::ControlInfo(char *value) {
    	values = new char*[1];
    	values[0] = value;
    };
    

    Meines Wissens ist da auch kein Fehler drin, der so was verursachen würde. Wenn ich vor der verhängnisvollen Zeile eine MessageBox values[0] ausgeben lasse, dann kommt der richtige String, hier gibt es also auch kein Problem, dass der Zeiger zwischendurch ungültig geworden ist.

    Das ganze ist mir ein Rätsel.



  • Woher bekommst du denn den char*-Wert, den du an den Ctor übergeben hast? Den darst du nur über delete[] freigeben, wenn er auch per new[] erzeugt wurde.

    PS: Muß es unbedingt ein char** sein? Oder wärst du mit einem vector<string> auch einverstanden?



  • Da du nur Speicher für das Array, aber nicht für die einzelnen Einträge reservierst, brauchst du es auch nicht freigeben.

    Die Übergabe mittels "char *" ist übrigens sehr unschön (oder soll die Klasse ControlInfo wirklich die Zeichenkette verändern dürfen?).
    Wenn schon, dann verwende einen "const char * const value".

    Aber warum verwendest du nicht std::vectorstd::string?

    Und zuguterletzt: warum allokierst du ein Array mit der Größe "1"???



  • Wow. Bei einem Topic mit char** darin hätte ich ja die übelsten Verleumdungen erwartet. Aber erstaunlich positive Antworten.

    zu CStoll: Den Fehler auf den Punkt getroffen. In der Tat ist das char-Array, dass ich da übergebe, statisch und nicht-dynamisch in einem Header deklariert und wird einfach am Ende des Programms wieder gelöscht. Also geht delete natürlich nicht.

    Mit den STL-Objekten habe ich überhaupt kein Problem. Ich denke mal, sie würden mir vergeben viel Zeit und Nerven ersparen. Aber irgendwie programmiere ich lieber in char-Arrays. Frag mich nicht warum, es ist komplett irrational.

    zu Th: Mit const habe ich es nicht so. Aber nicht, weil ich es für schlecht halte, sondern weil ich noch nicht dazu kam, mich da mal gescheit reinzulesen. Deinen Vorschlag werde ich langfristig wohl befolgen.

    Das Array mit der Größe 1 hat im Moment tatsächlich keinen größeren Sinn. Dieselbe Klasse soll aber auf anderer Einstellung auch mehrere Strings statt nur einen verwalten, je nachdem, wie benötigt. Da wollte ich nicht zwei Variablen aufmachen. Es lässt sich ja auch so ohne verkomplizierten Code lösen, indem im Falle nur eines Strings der einfach immer der erste Eintrag des potentiell auch größeren String-Arrays ist.



  • David Schneider schrieb:

    Wow. Bei einem Topic mit char** darin hätte ich ja die übelsten Verleumdungen erwartet. Aber erstaunlich positive Antworten.

    Ja, wir können durchaus konstruktiv sein - und flechten "bei einem Topic char**" höchstens den dezenten Hinweis ein, daß der betroffene mit std::vectorstd::string in ca. 99% der Fälle besser bedient ist 😉



  • CStoll schrieb:

    [...daß der betroffene mit std::vectorstd::string in ca. 99% der Fälle besser bedient ist 😉

    ... und das verbleibende Prozent füllt dann das const. 😉

    Gruß,

    Simon2.



  • Ich hab in derselben Anwendung ein Array von Objekten, dass mir dieselbe Assertion-Fehlermeldung, die ich schonmal hatte, ausgibt, wenn ich folgendes mache:

    // Deklaration im Klassen-Header
    ControlInfo *controlInfos;
    
    // Konstruktor der Klasse
    controlInfos = new ControlInfo[20];
    
    // eine Funktion
    controlInfos[0] = ControlInfo("Test", "Test"); // Assertion-Fehler
    

    Anscheinend bin ich heute in Sachen Zeigern sehr verwirrt.



  • Besitzt deine Klasse ControlInfo einen Zuweisungsoperator? Was macht der Konstruktor?
    Poste doch bitte mal Infos zu dieser Klasse.



  • David Schneider schrieb:

    Wow. Bei einem Topic mit char** darin hätte ich ja die übelsten Verleumdungen erwartet. Aber erstaunlich positive Antworten.

    lol



  • Nein, leider vergesse ich ständig den Zuweisungsoperator bei so was. Könnte seib, dass es daran liegt. Ich kann das allerdings erst morgen testen.

    Was die Klasse betrifft:

    class ControlInfo
    {
    public:
    	ControlInfo() { };
    	ControlInfo(char *name, char *value);
    
    	char *name;
    	CString *values;
    	int type;
    	CWnd *control;
    };
    
    ControlInfo::ControlInfo(char *name, char *value) {
    	control = NULL;
    
    	this->name = name;
    	values = new CString[1];
    	values[0] = CString(value);
    
        type = CT_EDIT_1;
    };
    

    Ich wüsste jetzt nicht, wieso ein mangelnder Zuweisunsoperator das zum Crashen bringen sollte, wenn das neu erstellte Objekt in das Array kopiert wird. Die Daten werden ja einfach alle kopiert. Da müssten die Zeiger aber nachher immer noch auf die selbe gültige Stelle zeigen.



  • oder auf die selbe ungültige Stelle. Wenn du dein Array von ControlInfo erstellst, werden die Elemente dort mit dem default-Konstruktor erstellt. Auf diese Elemente weist du dann deine neu erzeugten Instanzen zu. Dein default-Konstruktor stellt aber keinerlei Speicher bereit.
    Dann kopierst du nur Pointer und keine Strings. Dies kann gut gehen wenn du nur Stringliterale verwendest, man sollte sich aber nie darauf verlassen.
    Erzeuge im Konstruktor neuen Speicher und kopiere die Arrays lieber. Baue dir weiterhin einen Kopierkonstruktor und einen Zuweisungsoperator (über den CopyCTor) die dann ebenfalls kopieren.



  • Ich wunder mich bei der Sache nur darüber, dass der gesamte (zugegeben jetzt noch leere) operator = richtig ausgeführt wird, aber anscheinend beim Zurückgeben oder kurz danach abstürzt. Jedenfalls wird danach kein Befehl mehr ausgeführt, der Assertion-Fehler folgt direkt. Nur kommt dieser Fehler ja nicht in der Zuweisung zustande und es gibt auch keinen Anlass direkt danach, sowas hervorzurufen, wie etwa ein ungültiger Zeiger oder sowas. Außerdem benutze ich mittlerweile gar keine char-Arrays mehr, sondern habe jetzt nur noch ein Array von CString-Objekten. Da kann ja nun irgendwie beliebig wenig schief gehen, wenn ich einfach nur den Array-Zeiger kopieren lasse, wie standardmäßig üblich.

    EDIT: Ich hab den Fehler gefunden. Hing mit dem deallokieren der name-Variable, die immer noch ein char-Array war, zusammen. Da hatte ich hauptsächlich einfach String-Literale übergeben. Muss man das denn nicht auch deallokieren? Sowas steht doch auch nicht im Nirgendwo...



  • String-Literale musst du nicht selbst freigeben, darum kümmert sich schon der Compiler.
    Zu deinem default-Zuweisungsoperator. Du hast dort undefined behaviour. Das bedeutet eben, dass du nicht weißt was passieren wird. Es kann gleich abstürzen oder später oder gar nicht. Das kannst du nicht voraussagen, du kannst es aber vermeiden, wenn du sowas gar nicht erst machst.
    Ich glaube Hume sagte mal:
    undefined behaviour is undefined. 🙂
    Dein Konstruktor weiß schließlich nicht wo er die Strings her bekommt. Das können Stringliterale sein, auf dem Stack oder auf dem Heap erzeugte Array, Pointer aus Stringklassen etc. Da ist es halt am besten die Strings intern zu kopieren, da muß deinenKlasse dann nichts mehr über die Strings wissen.


Anmelden zum Antworten