Invalid Heap Pointer nach Aufruf von new char[]



  • Ohne dir jetzt nahe treten zu wollen, aber das, was du an Code präsentiert hast ist wirklich nicht schön.

    - Verwendung von struct statt class . Damit sind alle Attribute des SStrings von aussen zugreifbar und verletzen das Data hiding Prinzip. Damit sind so unsaubere Hacks wie dein delete[] möglich. Dass du darauf keine schönen Antworten bekommst ist nachvollziehbar.

    - deine Klasse hat weder Kopier- noch Zuweisungsoperator, auch der Destruktor fehlt. Das schreit förmlich nach Zugriffsverletzungen und mehrfachen Speicherfreigaben.



  • wird acString auch irgendwo mal initialisiert?



  • Ok, ich glaube ich erwarte zu viel von den Benutzern dieses Forums und von dem Forum selbst.
    Die Möglichkeit, dass Leser unvoreingenommen die vorherigen Beiträge durchlesen ist scheinbar nicht gegeben. Ich finde es schade, dass jegliche Art des Querdenkens nieder gemacht wird, selbst wenn die kommentierenden Benutzer nichts zur Lösung des Problems beizutragen haben.
    Das eben solche unvoreingenommenen Kommentare sogar von einem Moderator kommen lässt hoffentlich nicht auf alle zurückschließen.

    Nun zur Erklärung:

    Wieso ist ein [planloses] Pointergefrickel eine valide Alternative zu std::string? [...]

    Man kann noch Anwendungsfälle konstruieren, in denen klassische char-Arrays statischer Größe eine Alternative mit gewissen Vorteilen sind.

    std:string, was einzig eine Typdefinition von basic_string<char> ist, bietet recht wenig Operationen um den String auf bestimmte Arten zu modifizieren, vor allem, wenn die Art der Modifikation sehr speziell ist. Ein sehr großer Nachteil von std::string gegenüber C-Strings ist für jeden der sein Fach beherrscht die verloren gegangene Zeigerarithmetik, hier so schön schon beschrieben als "planloses Pointergefrickel".

    Aber wenn du selber einen dynamischen String schreibst, dann bist du eben selber schuld, wenn es unnötige Arbeit ist und trotzdem nicht funktioniert.

    Nur weil ich die Struktur in diesem Beispiel "SString" genannt habe, heißt das nicht, dass ich einen dynamischen String schreiben will. Genauso gut hätte die Struktur auch "Wolpertinger" oder "EierlegendeWollMilchSau" heißen können.

    Nein, so macht man das nie! Man verteilt niemals Verantwortung zwischen verschiedenen Nutzungsebenen! Ein Objekt hat sich um seinen eigenen Kram zu kümmern, sich nicht auf fremden Code zu verlassen und räumt sauber hinter sich auf. Das ist in C++ durch die Konstruktoren und Destruktoren kinderleicht zu programmieren und bei dieser Art der Programmierung kann gar nichts schiefgehen.

    Es mag auf Manche zutreffen dies niemals zu tun. Diese Struktur dient allerdings nur einer einzigen Klasse als Speicherstruktur, und ist für keine andere Klasse von Relevanz. Diese eine Klasse ist auch die Einzige, die Instanzen dieser Struktur anlegt, bearbeitet und löscht, und wenn mir jemand erklären will, dass soetwas niemals gemacht werden würde, der sollte mal besser recherchieren.
    Hier trifft es leider wieder zu, dass nicht richtig gelesen wird. Die Struktur, die hier "SString" heißt, besitzt einen Konstruktor und einen Destruktor, was in dieser Situation allerdings keine Rolle spielt, da ich nicht die Struktur an sich, sondern nur einen Teil der Struktur, nämlich den beinhalteten String, löschen will.

    Ohne dir jetzt nahe treten zu wollen, aber das, was du an Code präsentiert hast ist wirklich nicht schön.

    - Verwendung von struct statt class. Damit sind alle Attribute des SStrings von aussen zugreifbar und verletzen das Data hiding Prinzip. Damit sind so unsaubere Hacks wie dein delete[] möglich. Dass du darauf keine schönen Antworten bekommst ist nachvollziehbar.

    - deine Klasse hat weder Kopier- noch Zuweisungsoperator, auch der Destruktor fehlt. Das schreit förmlich nach Zugriffsverletzungen und mehrfachen Speicherfreigaben.

    Der Code, den ich hier präsentiert habe, ist genau der relevante Teil. Nicht mehr und auch nicht weniger. Dass ich struct statt class genommen hab, war volle Absicht und geht eventuell aus dem vorherigen Absatz hervor. Diese Struktur nimmt nur den Platz einer Datenstruktur ein, ähnlich einem Integer Wert. Würde ich an dieser Stelle eine Klasse erstellen, wäre das so, als ob ich eine Klasse "Integer" für normale "int" Werte erstelle.
    Kopier- und Zuweisungsoperator fehlen nicht nur hier, sondern bewusst komplett, da es programmintern keinen Sinn macht eine Instanz dieser Struktur einer Anderen zuzuweisen. Konstruktor und Destruktor sind mit allen Sicherheiten versehen, um mehrfaches Löschen zu verhindern, sind an dieser Stelle allerdings nicht von Belang.

    wird acString auch irgendwo mal initialisiert?

    Für delete wäre es belanglos, ob acString NULL oder ein C-String ist. Da diese Funktion nur aufgerufen wird, wenn acString überhaupt einen Wert hat, ist acString auch in jedem Fall initialisiert.

    Wenn du selber einen String schreibst und diese Verfahren nicht berücksichtigst, dann bist du eben selber schuld, wenn es unnötige Arbeit ist und trotzdem nicht funktioniert.

    Nach einer Unterhaltung mit einem kompetenten Ansprechpartner und unter der Zuhilfe eines Speicherüberwachungstools hat sich herausgestellt, dass nicht ich, sondern mein System für den Programmabsturz verantwortlich ist. Das System reallokiert aus irgendeinem Grund den Speicher nach der Schleife, womit die Zeiger ungültig werden.
    Vor dem Beginn des Projekts wurde zwischen std::string und char* abgewägt und das Ergebnis ist klar zu gunsten von char* ausgefallen, da es weitaus elegantere Methoden zur Modifikation als std::string bietet.

    Da das Problem jetzt duch eine auf reiner Logik basierten Änderung so funktioniert wie gedacht, kümmert es mich nicht weiter, aber solch eine subjektive und voreingenommene Antwort mit einer derat offensichtlichen Unterstellung von Unwissenheit - ich will nicht sagen Dummheit - von einem Moderator halte ich für sehr anmaßend und überheblich.

    Für Alle, die die Lösung interessiert:
    Statt in jedem Schleifendurchlauf neuen Speicher zu reservieren (was das System offensichtlich nicht besonders schätzt) wurden nun vor der Schleife zwei gleich große char-arrays angelegt, die so groß sind, dass auch der schlechteste Fall der Neukombination der Zeichenkette problemlos hineinpasst (logisch berechenbar).
    Die Schleife liest aus dem ersten Buffer und schreibt in den Zweiten, danach wird der zweite Buffer in den Ersten kopiert, damit die Neue Zeichenkette wieder überprüft werden kann.
    Wenn die Schleife fertig ist, wird Speicher in mit der finalen Größe der Zeichenkette angefordert, acString gelöscht und auf den neuen Speicher gesetzt. Funktioniert einwandfrei ohne jegliches Speicherleck.



  • Diego Dela Vega schrieb:

    Ok, ich glaube ich erwarte zu viel von den Benutzern dieses Forums und von dem Forum selbst.
    Die Möglichkeit, dass Leser unvoreingenommen die vorherigen Beiträge durchlesen ist scheinbar nicht gegeben. Ich finde es schade, dass jegliche Art des Querdenkens nieder gemacht wird, selbst wenn die kommentierenden Benutzer nichts zur Lösung des Problems beizutragen haben.
    Das eben solche unvoreingenommenen Kommentare sogar von einem Moderator kommen lässt hoffentlich nicht auf alle zurückschließen.

    Heul doch woanders rum.

    Diego Dela Vega schrieb:

    Nun zur Erklärung:

    Wieso ist ein [planloses] Pointergefrickel eine valide Alternative zu std::string? [...]

    Man kann noch Anwendungsfälle konstruieren, in denen klassische char-Arrays statischer Größe eine Alternative mit gewissen Vorteilen sind.

    std:string, was einzig eine Typdefinition von basic_string<char> ist, bietet recht wenig Operationen um den String auf bestimmte Arten zu modifizieren, vor allem, wenn die Art der Modifikation sehr speziell ist.

    Du hast einfach nur keine Ahnung, das ist das Problem.

    Diego Dela Vega schrieb:

    Ein sehr großer Nachteil von std::string gegenüber C-Strings ist für jeden der sein Fach beherrscht die verloren gegangene Zeigerarithmetik, hier so schön schon beschrieben als "planloses Pointergefrickel".

    Unsinn, string hat Iteratoren, die Zeigern überlegen sind.

    Diego Dela Vega schrieb:

    em Grund den Speicher nach der Schleife, womit die Zeiger ungültig werden.
    Vor dem Beginn des Projekts wurde zwischen std::string und char* abgewägt und das Ergebnis ist klar zu gunsten von char* ausgefallen, da es weitaus elegantere Methoden zur Modifikation als std::string bietet.

    Planloses Gefrickel ist also elegant?

    Diego Dela Vega schrieb:

    Da das Problem jetzt duch eine auf reiner Logik basierten Änderung so funktioniert wie gedacht, kümmert es mich nicht weiter, aber solch eine subjektive und voreingenommene Antwort mit einer derat offensichtlichen Unterstellung von Unwissenheit - ich will nicht sagen Dummheit - von einem Moderator halte ich für sehr anmaßend und überheblich.

    Jetzt hast du ja deine auf reinem Schwachsinn basierende Lösung.

    Diego Dela Vega schrieb:

    Für Alle, die die Lösung interessiert:
    Statt in jedem Schleifendurchlauf neuen Speicher zu reservieren (was das System offensichtlich nicht besonders schätzt) wurden nun vor der Schleife zwei gleich große char-arrays angelegt, die so groß sind, dass auch der schlechteste Fall der Neukombination der Zeichenkette problemlos hineinpasst (logisch berechenbar).

    Man hätte auch einfach string nehmen können und das Problem wäre nie entstanden.

    Diego Dela Vega schrieb:

    Die Schleife liest aus dem ersten Buffer und schreibt in den Zweiten, danach wird der zweite Buffer in den Ersten kopiert, damit die Neue Zeichenkette wieder überprüft werden kann.
    Wenn die Schleife fertig ist, wird Speicher in mit der finalen Größe der Zeichenkette angefordert, acString gelöscht und auf den neuen Speicher gesetzt. Funktioniert einwandfrei ohne jegliches Speicherleck.

    Also wenn das kein Gefrickel ist weiß ich auch nicht.



  • Diego Dela Vega schrieb:

    Hallo,
    ich schreibe gerade an einem kleinen Programmteil zum bearbeiten von Zeichenketten. Ziel soll erstmal sein, alle Strings, also alle "..." aus der Zeichenkette zu ziehen, und dafür Variablen einzusetzen. Mein Code zur Rekombination der Zeichenketten funktioniert, allerdings gibts bei der Zuweisung, oder viel mehr beim Löschen des vorher zugewiesenen Speichers, eine "Invalid Heap Pointer" Fehlermeldung, wenn in einer Zeichenkette mehr als einmal "..." ersetzt werden soll.

    Ich habe den Code mal stark auf das Wesentliche reduziert, also bitte nicht wundern, wenn etwas "unsinnig" erscheint.

    struct SString
    {
        char* acString;
        // normal ist das hier größer, der rest spielt aber keine rolle
    }
    
    bool Unstring(SString* sString)
    {
        while (strchr(sString->acString, '\"'))
        {
            // hier wird geprüft, ob es zwei " gibt und die anzahl der zeichen vor und zwischen den " berechnet
            // danach wird der teil zwischen den " kopiert und zwischengespeichert ... unrelevant
    
            // ab hier kommt der relevante teil
            char* acNewString = new char[<berechnete benötigte länge> + 1];
            // Stringrekombination, mehrfacher aufruf von strcpy_s
    
            // und beim zweiten durchlauf wird bei delete der fehler ausgegeben ...
            delete[] sString->acString;
            sString->acString = acNewString;
        }
    }
    

    Ich habe schon schrittweise debuggt. Und wenn das delete auskommentiert ist, funktioniert die Funktion genau so, wie ich mir das vorstelle. Allerdings ist das dann ein Speicherleck, was ich gerne verhindern möchte ...

    Wer noch Informationen braucht, ich benutze Visual Studio 2010 unter Windows 7 64Bit

    Ich ignoriere mal die anderen Kommentare und versuche mal auf die Frage zu antworten, ohne zu urteilen, ob das Sinn macht oder nicht. Darum geht es hier ja eigenlich nicht.

    Du alloziiert den neuen Speicher und weist ihn sString->acString zu. Beim 2. Durchlauf rufst Du damit strchr auf. Aber der String ist nur alloziiert und enthält nur zufällige Zeichen. Da macht das strchr wenig Sinn.


  • Mod

    tntnet schrieb:

    Diego Dela Vega schrieb:

    Hallo,
    ich schreibe gerade an einem kleinen Programmteil zum bearbeiten von Zeichenketten. Ziel soll erstmal sein, alle Strings, also alle "..." aus der Zeichenkette zu ziehen, und dafür Variablen einzusetzen. Mein Code zur Rekombination der Zeichenketten funktioniert, allerdings gibts bei der Zuweisung, oder viel mehr beim Löschen des vorher zugewiesenen Speichers, eine "Invalid Heap Pointer" Fehlermeldung, wenn in einer Zeichenkette mehr als einmal "..." ersetzt werden soll.

    Ich habe den Code mal stark auf das Wesentliche reduziert, also bitte nicht wundern, wenn etwas "unsinnig" erscheint.

    struct SString
    {
        char* acString;
        // normal ist das hier größer, der rest spielt aber keine rolle
    }
    
    bool Unstring(SString* sString)
    {
        while (strchr(sString->acString, '\"'))
        {
            // hier wird geprüft, ob es zwei " gibt und die anzahl der zeichen vor und zwischen den " berechnet
            // danach wird der teil zwischen den " kopiert und zwischengespeichert ... unrelevant
    
            // ab hier kommt der relevante teil
            char* acNewString = new char[<berechnete benötigte länge> + 1];
            // Stringrekombination, mehrfacher aufruf von strcpy_s
    
            // und beim zweiten durchlauf wird bei delete der fehler ausgegeben ...
            delete[] sString->acString;
            sString->acString = acNewString;
        }
    }
    

    Ich habe schon schrittweise debuggt. Und wenn das delete auskommentiert ist, funktioniert die Funktion genau so, wie ich mir das vorstelle. Allerdings ist das dann ein Speicherleck, was ich gerne verhindern möchte ...

    Wer noch Informationen braucht, ich benutze Visual Studio 2010 unter Windows 7 64Bit

    Ich ignoriere mal die anderen Kommentare und versuche mal auf die Frage zu antworten, ohne zu urteilen, ob das Sinn macht oder nicht. Darum geht es hier ja eigenlich nicht.

    Du alloziiert den neuen Speicher und weist ihn sString->acString zu. Beim 2. Durchlauf rufst Du damit strchr auf. Aber der String ist nur alloziiert und enthält nur zufällige Zeichen. Da macht das strchr wenig Sinn.

    Das Problem ist, dass der eigentlich interessante (weil mutmaßlich fehlerhafte) Teil des Codes entfernt wurde. Damit ist die gestellte Frage nicht ohne großes Nachfragen beantwortbar. Das macht den ganzen Thread von vornherein uninteressant, jedenfalls ist es ein ist es ein KO-Kriterium, wenn ich mich entscheide, ob ich ich mich an der Diskussion beteilige oder nicht. Es ist auch nicht meine Aufgabe, einen wie-stelle-ich-Fragen-richtig-Link zu platzieren.



  • std::string-Instanzen verwalten ihren Speicher übrigens üblicherweise Referenz-gezählt. Das kann dein char* nicht.



  • camper schrieb:

    Das Problem ist, dass der eigentlich interessante (weil mutmaßlich fehlerhafte) Teil des Codes entfernt wurde. Damit ist die gestellte Frage nicht ohne großes Nachfragen beantwortbar. Das macht den ganzen Thread von vornherein uninteressant, jedenfalls ist es ein ist es ein KO-Kriterium, wenn ich mich entscheide, ob ich ich mich an der Diskussion beteilige oder nicht. Es ist auch nicht meine Aufgabe, einen wie-stelle-ich-Fragen-richtig-Link zu platzieren.

    Das verstehe ich aber nicht. Der fehlerhafte Teil des Codes ist doch da! Ich habe das Problem doch erläutert. Bei dem ersten Durchlauf wird der Speicher zwar reserviert aber nicht befüllt. Beim nächsten wird strchr auf den nicht befüllten Speicher aufgerufen. Wobei es erst mal ungeklärt ist, warum dann ein Invalid Heap Pointer kommt, aber der Fehler ist im Code zu erkennen.


  • Mod

    tntnet schrieb:

    camper schrieb:

    Das Problem ist, dass der eigentlich interessante (weil mutmaßlich fehlerhafte) Teil des Codes entfernt wurde. Damit ist die gestellte Frage nicht ohne großes Nachfragen beantwortbar. Das macht den ganzen Thread von vornherein uninteressant, jedenfalls ist es ein ist es ein KO-Kriterium, wenn ich mich entscheide, ob ich ich mich an der Diskussion beteilige oder nicht. Es ist auch nicht meine Aufgabe, einen wie-stelle-ich-Fragen-richtig-Link zu platzieren.

    Das verstehe ich aber nicht. Der fehlerhafte Teil des Codes ist doch da! Ich habe das Problem doch erläutert. Bei dem ersten Durchlauf wird der Speicher zwar reserviert aber nicht befüllt. Beim nächsten wird strchr auf den nicht befüllten Speicher aufgerufen. Wobei es erst mal ungeklärt ist, warum dann ein Invalid Heap Pointer kommt, aber der Fehler ist im Code zu erkennen.

    // Stringrekombination, mehrfacher aufruf von strcpy_s
    

    suggeriert, dass da etwas mit dem Speicher angestellt wird. Falls doch nicht... dann ist der Kommentar erst recht fehl am Platz.



  • 314159265358979 schrieb:

    std::string-Instanzen verwalten ihren Speicher übrigens üblicherweise Referenz-gezählt. Das kann dein char* nicht.

    Macht zumindestens die libstdc++. Zb der MSVC nutzt aber stattdessen Short String Optimization, alle Strings kleiner 16 Chars benötigen keine Allocation. Finde ich persönlich besser, der Nutzen von Copy-on-write ist oft eher gering ...



  • 314159265358979 schrieb:

    std::string-Instanzen verwalten ihren Speicher übrigens üblicherweise Referenz-gezählt. Das kann dein char* nicht.

    Die gcc Version macht das so, ich meine aber, dass mindestens MSVC++ ein normales dynamisches array mit short string optimization (oder wie man sowas nennen will) verwendet.
    EDIT: zu langsam 🙂
    Dass man hier so sehr auf std::string pocht wundert mich aber. Ich finde das design von std::string nicht besonders gut. Eine Aufteilung in zwei ineinander konvertierbare Klassen std::string_buffer und std::immutable_string_reference (mit besseren Namen...) wäre meiner Ansicht nach klüger gewesen. substr würde nur dann kopieren, wenn es wirklich nötig ist. Eine immutable_string_reference als Funktionsparameter hätte den Vorteil, dass man ohne zu kopieren einen c string oder ein literal übergeben kann.
    Natürlich könnte man sich ins Knie schießen, indem man den buffer einer reference ändert. Aber das sollte in c++ kein Problem sein, schließlich geht man davon aus, dass ein c++ Programmierer weiß, was er tut.



  • GorbGorb schrieb:

    Dass man hier so sehr auf std::string pocht wundert mich aber. Ich finde das design von std::string nicht besonders gut.

    Das std::string nicht unbedingt das beste Design hat, mag sein. C-Strings haben aber viele Fehlerquellen, die man mit dem std::string umschifft. Wenn man wirklich andere Funktionatlitäten braucht, muss man sich überlegen ob man die Gefahren den std::string-Design vorzieht (Oder die Gefahren zumindest hinter eine Schnittstelle kapselt die nach Möglichkeit die falsche Verwendung verhindert).



  • Wie soll ein String mit < 16 Zeichen keine Allokation benötigen?



  • 314159265358979 schrieb:

    Wie soll ein String mit < 16 Zeichen keine Allokation benötigen?

    Der std::string von msvc++ hat halt intern ein char buffer[16] . Erst wenn der voll ist wird der heap bemüht.



  • Was für ne Verschwendung oO


  • Mod

    314159265358979 schrieb:

    Was für ne Verschwendung oO

    Du verstehst nicht. Das ist kein zusätzliches Array. Stell dir eine Art union vor. Wenn die Stringlänge kleiner ist als die internen Verwaltungsdaten des Strings, dann werden diese Daten wie ein char[] benutzt. Wird der String länger, schaltet er um auf Datenfeld im Heap.



  • Achso, okay. Das ist was anderes. Interessanter Trick.



  • SeppJ schrieb:

    314159265358979 schrieb:

    Was für ne Verschwendung oO

    Du verstehst nicht. Das ist kein zusätzliches Array. Stell dir eine Art union vor. Wenn die Stringlänge kleiner ist als die internen Verwaltungsdaten des Strings, dann werden diese Daten wie ein char[] benutzt. Wird der String länger, schaltet er um auf Datenfeld im Heap.

    Interessant. Ich war davon ausgegangen, dass sie mindestens den data_begin und data_end ptr auf jeden Fall auch bei kleinen strings behalten würden, um operator[], begin und end möglichst effizient zu halten. Weißt du was die intern genau machen? (habe den vc++ nicht mehr installiert)


  • Mod

    GorbGorb schrieb:

    Interessant. Ich war davon ausgegangen, dass sie mindestens den data_begin und data_end ptr auf jeden Fall auch bei kleinen strings behalten würden, um operator[], begin und end möglichst effizient zu halten. Weißt du was die intern genau machen? (habe den vc++ nicht mehr installiert)

    Wie das nun haargenau ist, weiß ich nicht und ich kann's ebenfalls nicht nachschauen. Gut möglich, dass du Recht hast.



  • Zum Thema standard libraries würde ich noch anmerken, dass die nicht immer besonders effizient geschrieben sind. Mein MinGW mit gcc 4.6 macht zum Beispiel keine small object optimization bei std::function, obwohl sizeof( std::function<...> ) genauso groß ist wie bei boost::function. (das ist finde ich echt ein Ärgernis... die hätten doch einfach die Version von boost übernehmen können)


Anmelden zum Antworten