c - Warum erhalte ich mit realloc () einen doppelten freien Fehler?

Translate

Ich habe versucht, eine String-Ersetzungsfunktion in C zu schreiben, die auf a funktioniertchar *, die mit zugewiesen wurdemalloc(). Es ist insofern etwas anders, als es Zeichenfolgen findet und ersetzt, anstatt Zeichen in der Startzeichenfolge.

Es ist trivial zu tun, wenn die Such- und Ersetzungszeichenfolgen dieselbe Länge haben (oder die Ersetzungszeichenfolge kürzer als die Suchzeichenfolge ist), da mir genügend Speicherplatz zugewiesen ist. Wenn ich versuche zu benutzenrealloc()Ich erhalte eine Fehlermeldung, die mir sagt, dass ich ein Double Free mache - was ich nicht sehe, wie ich bin, da ich nur benutzerealloc().

Vielleicht hilft ein kleiner Code:

void strrep(char *input, char *search, char *replace) {
    int searchLen = strlen(search);
    int replaceLen = strlen(replace);
    int delta = replaceLen - searchLen;
    char *find = input;

    while (find = strstr(find, search)) {

        if (delta > 0) {
            realloc(input, strlen(input) + delta);
            find = strstr(input, search);            
        }

        memmove(find + replaceLen, find + searchLen, strlen(input) - (find - input));
        memmove(find, replace, replaceLen);
    }
}

Das Programm funktioniert, bis ich es versucherealloc()in einem Fall, in dem die ersetzte Zeichenfolge länger als die ursprüngliche Zeichenfolge ist. (Es funktioniert immer noch, es spuckt nur Fehler und das Ergebnis aus).

Wenn es hilft, sieht der aufrufende Code folgendermaßen aus:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void strrep(char *input, char *search, char *replace);

int main(void) {
    char *input = malloc(81);

    while ((fgets(input, 81, stdin)) != NULL) {
        strrep(input, "Noel", "Christmas");
    }
}
This question and all comments follow the "Attribution Required."

Alle Antworten

Translate

In der Regel sollten Sienoch nieFühren Sie eine kostenlose oder Neuzuweisung für einen vom Benutzer bereitgestellten Puffer durch. Sie wissen nicht, wo der Benutzer den Speicherplatz zugewiesen hat (in Ihrem Modul, in einer anderen DLL), sodass Sie keine der Zuweisungsfunktionen für einen Benutzerpuffer verwenden können.

Vorausgesetzt, Sie können jetzt keine Neuzuweisung innerhalb Ihrer Funktion vornehmen, sollten Sie das Verhalten ein wenig ändern, z. B. nur eine Ersetzung, damit der Benutzer die resultierende maximale Länge der Zeichenfolge berechnen und einen Puffer bereitstellen kann, der lang genug für diese Funktion ist Ersatz erfolgen.

Dann könnten Sie eine andere Funktion erstellen, um die mehrfachen Ersetzungen durchzuführen, aber Sie müssen den gesamten Speicherplatz für die resultierende Zeichenfolge zuweisen und die Benutzereingabezeichenfolge kopieren. Anschließend müssen Sie eine Möglichkeit angeben, die von Ihnen zugewiesene Zeichenfolge zu löschen.

Ergebend:

void  strrep(char *input, char *search, char *replace);
char* strrepm(char *input, char *search, char *replace);
void  strrepmfree(char *input);
Quelle
Translate

Zunächst einmal, tut mir leid, dass ich zu spät zur Party komme. Dies ist meine erste Stackoverflow-Antwort. :) :)

Wie bereits erwähnt, können Sie beim Aufruf von realloc () möglicherweise den Zeiger auf den neu zugewiesenen Speicher ändern. In diesem Fall wird das Argument "Zeichenfolge" ungültig. Selbst wenn Sie es neu zuweisen, wird die Änderung nach Beendigung der Funktion nicht mehr angezeigt.

Um das OP zu beantworten, gibt realloc () einen Zeiger auf den neu neu zugewiesenen Speicher zurück. Der Rückgabewert muss irgendwo gespeichert werden. Im Allgemeinen würden Sie dies tun:

data *foo = malloc(SIZE * sizeof(data));
data *bar = realloc(foo, NEWSIZE * sizeof(data));

/* Test bar for safety before blowing away foo */
if (bar != NULL)
{
   foo = bar;
   bar = NULL;
}
else
{
   fprintf(stderr, "Crap. Memory error.\n");
   free(foo);
   exit(-1);
}

Wie TyBoer betont, können Sie den Wert des Zeigers, der als Eingabe für diese Funktion übergeben wird, nicht ändern. Sie können alles zuweisen, was Sie möchten, aber die Änderung wird am Ende der Funktion nicht mehr gültig sein. Im folgenden Block kann "Eingabe" nach Abschluss der Funktion ein ungültiger Zeiger sein oder nicht:

void foobar(char *input, int newlength)
{
   /* Here, I ignore my own advice to save space. Check your return values! */
   input = realloc(input, newlength * sizeof(char));
}

Mark versucht dies zu umgehen, indem er den neuen Zeiger als Ausgabe der Funktion zurückgibt. Wenn Sie dies tun, muss der Anrufer den Zeiger, den er für die Eingabe verwendet hat, nie wieder verwenden. Wenn es mit dem Rückgabewert übereinstimmt, haben Sie zwei Zeiger auf dieselbe Stelle und müssen nur an einem von ihnen free () aufrufen. Wenn sie nicht übereinstimmen, zeigt der Eingabezeiger jetzt auf den Speicher, der möglicherweise dem Prozess gehört oder nicht. Eine Dereferenzierung kann einen Segmentierungsfehler verursachen.

Sie können einen Doppelzeiger für die Eingabe verwenden, wie folgt:

void foobar(char **input, int newlength)
{
   *input = realloc(*input, newlength * sizeof(char));
}

Wenn der Aufrufer irgendwo ein Duplikat des Eingabezeigers hat, ist dieses Duplikat möglicherweise immer noch ungültig.

Ich denke, die sauberste Lösung besteht darin, die Verwendung von realloc () zu vermeiden, wenn versucht wird, die Eingabe des Funktionsaufrufers zu ändern. Geben Sie einfach malloc () einen neuen Puffer ein, geben Sie diesen zurück und lassen Sie den Aufrufer entscheiden, ob der alte Text freigegeben werden soll oder nicht. Dies hat den zusätzlichen Vorteil, dass der Anrufer die ursprüngliche Zeichenfolge behalten kann!

Quelle
Translate

Nur ein Schuss in die Dunkelheit, weil ich es noch nicht ausprobiert habe, aber wenn Sie es neu zuweisen, wird der Zeiger ähnlich wie bei malloc zurückgegeben. Da realloc den Zeiger bei Bedarf verschieben kann, arbeiten Sie höchstwahrscheinlich mit einem ungültigen Zeiger, wenn Sie Folgendes nicht tun:

input = realloc(input, strlen(input) + delta);
Quelle
Translate

Jemand anderes entschuldigte sich dafür, dass er zu spät zur Party gekommen war - vor zweieinhalb Monaten. Na ja, ich verbringe ziemlich viel Zeit mit Software-Archäologie.

Ich bin daran interessiert, dass niemand den Speicherverlust im Originaldesign oder den Fehler "Off-by-One" explizit kommentiert hat. Und es war die Beobachtung des Speicherverlusts, die mir genau sagt, warum Sie den doppelten freien Fehler erhalten (weil Sie, um genau zu sein, denselben Speicher mehrmals freigeben - und dies nach dem Trampeln über den bereits freigegebenen Speicher).

Bevor ich die Analyse durchführe, stimme ich denen zu, die sagen, dass Ihre Schnittstelle weniger als hervorragend ist. Wenn Sie sich jedoch mit den Problemen mit Speicherverlust / Trampling befasst und die Anforderung "Speicher muss zugewiesen werden" dokumentiert haben, kann dies "OK" sein.

Was sind die Probleme? Nun, Sie übergeben einen Puffer an realloc (), und realloc () gibt Ihnen einen neuen Zeiger auf den Bereich zurück, den Sie verwenden sollten - und Sie ignorieren diesen Rückgabewert. Infolgedessen hat realloc () wahrscheinlich den ursprünglichen Speicher freigegeben, und dann übergeben Sie ihm denselben Zeiger erneut, und es wird beanstandet, dass Sie denselben Speicher zweimal freigeben, weil Sie den ursprünglichen Wert erneut an ihn übergeben. Dies führt nicht nur zu Speicherverlusten, sondern bedeutet auch, dass Sie weiterhin den ursprünglichen Speicherplatz verwenden - und John Downeys Schuss im Dunkeln weist darauf hin, dass Sie realloc () missbrauchen, betont jedoch nicht, wie stark Sie dies tun. Es gibt auch einen Fehler nach dem anderen, weil Sie nicht genügend Speicherplatz für den NUL '\ 0' zuweisen, der die Zeichenfolge beendet.

Der Speicherverlust tritt auf, weil Sie keinen Mechanismus bereitstellen, um den Aufrufer über den letzten Wert der Zeichenfolge zu informieren. Da Sie die ursprüngliche Zeichenfolge und das Leerzeichen danach immer wieder mit Füßen getreten haben, hat der Code anscheinend funktioniert. Wenn Ihr aufrufender Code jedoch den Speicherplatz freigegeben hat, wird auch ein doppelter Fehler angezeigt, oder es wird möglicherweise ein Core-Dump oder ein gleichwertiger Fehler ausgegeben Die Speichersteuerungsinformationen werden vollständig verschlüsselt.

Ihr Code schützt auch nicht vor unbestimmtem Wachstum. Ersetzen Sie "Noel" durch "Joyeux Noel". Jedes Mal würden Sie 7 Zeichen hinzufügen, aber Sie würden ein anderes Noel im ersetzten Text finden und es erweitern und so weiter und so fort. Mein Fixup (unten) behebt dieses Problem nicht. Die einfache Lösung besteht wahrscheinlich darin, zu überprüfen, ob die Suchzeichenfolge in der Ersetzungszeichenfolge angezeigt wird. Eine Alternative besteht darin, die Ersetzungszeichenfolge zu überspringen und die Suche danach fortzusetzen. Der zweite hat einige nicht triviale Codierungsprobleme zu lösen.

Meine vorgeschlagene Überarbeitung Ihrer aufgerufenen Funktion lautet also:

char *strrep(char *input, char *search, char *replace) {
    int searchLen = strlen(search);
    int replaceLen = strlen(replace);
    int delta = replaceLen - searchLen;
    char *find = input;

    while ((find = strstr(find, search)) != 0) {
        if (delta > 0) {
            input = realloc(input, strlen(input) + delta + 1);
            find = strstr(input, search);            
        }

        memmove(find + replaceLen, find + searchLen, strlen(input) + 1 - (find - input));
        memmove(find, replace, replaceLen);
    }

    return(input);
}

Dieser Code erkennt keine Speicherzuordnungsfehler - und stürzt wahrscheinlich ab (wenn nicht, geht Speicher verloren), wenn realloc () fehlschlägt. In Steve Maguires Buch "Writing Solid Code" finden Sie ausführliche Informationen zu Speicherverwaltungsproblemen.

Quelle
Translate

Beachten Sie, dass Sie versuchen, Ihren Code zu bearbeiten, um die HTML-Escape-Codes zu entfernen.

Nun, obwohl es eine Weile her ist, dass ich C / C ++ verwendet habe, verwendet Realloc, das wächst, den Speicherzeigerwert nur wieder, wenn nach Ihrem ursprünglichen Block Platz im Speicher ist.

Betrachten Sie zum Beispiel Folgendes:

(xxxxxxxxxx ..........)

Wenn Ihr Zeiger auf das erste x zeigt, und. bedeutet freien Speicherplatz, und wenn Sie die Speichergröße, auf die Ihre Variable zeigt, um 5 Byte erhöhen, ist dies erfolgreich. Dies ist natürlich ein vereinfachtes Beispiel, da Blöcke zur Ausrichtung auf eine bestimmte Größe aufgerundet werden, aber trotzdem.

Wenn Sie jedoch später versuchen, es um weitere 10 Byte zu vergrößern, und nur 5 verfügbar sind, muss der Block im Speicher verschoben und der Zeiger aktualisiert werden.

In Ihrem Beispiel übergeben Sie der Funktion jedoch einen Zeiger auf das Zeichen und keinen Zeiger auf Ihre Variable. Während die strrep-Funktion die verwendete Variable möglicherweise intern anpassen kann, handelt es sich um eine lokale Variable für die strrep-Funktion und Ihr aufrufender Code wird mit dem ursprünglichen Wert der Zeigervariable belassen.

Dieser Zeigerwert wurde jedoch freigegeben.

In Ihrem Fall ist die Eingabe der Schuldige.

Ich würde jedoch einen anderen Vorschlag machen. In deinem Fall sieht es so ausEingangVariable wird tatsächlich eingegeben, und wenn ja, sollte sie überhaupt nicht geändert werden.

Ich würde daher versuchen, einen anderen Weg zu finden, um das zu tun, was Sie tun möchten, ohne sich zu ändernEingang, da solche Nebenwirkungen schwer aufzuspüren sind.

Quelle
Translate

Das scheint zu funktionieren;

char *strrep(char *string, const char *search, const char *replace) {
    char *p = strstr(string, search);

    if (p) {
        int occurrence = p - string;
        int stringlength = strlen(string);
        int searchlength = strlen(search);
        int replacelength = strlen(replace);

        if (replacelength > searchlength) {
            string = (char *) realloc(string, strlen(string) 
                + replacelength - searchlength + 1);
        }

        if (replacelength != searchlength) {
            memmove(string + occurrence + replacelength, 
                        string + occurrence + searchlength, 
                        stringlength - occurrence - searchlength + 1);
        }

        strncpy(string + occurrence, replace, replacelength);
    }

    return string;
}

Seufz, gibt es überhaupt eine Postleitzahl, ohne zu saugen?

Quelle
Translate

Realloc ist seltsam, kompliziert und sollte nur verwendet werden, wenn viel Speicherplatz pro Sekunde verwendet wird. dh - wo es Ihren Code tatsächlich schneller macht.

Ich habe Code wo gesehen

realloc(bytes, smallerSize);

wurde verwendet und gearbeitet, um die Größe des Puffers zu ändern und ihn kleiner zu machen. Hat ungefähr eine Million Mal gearbeitet, dann hat realloc aus irgendeinem Grund entschieden, dass es Ihnen eine schöne neue Kopie geben würde, selbst wenn Sie den Puffer verkürzen würden. Sie stürzen also eine halbe Sekunde nach dem Zufall an einem zufälligen Ort ab.

Verwenden Sie immer den Rückgabewert von realloc.

Quelle
Translate

Meine schnellen Hinweise.

Anstatt von:
void strrep(char *input, char *search, char *replace)
Versuchen:
void strrep(char *&input, char *search, char *replace)

und als im Körper:
input = realloc(input, strlen(input) + delta);

Lesen Sie im Allgemeinen über das Übergeben von Funktionsargumenten als Werte / Referenz und Beschreibung von realloc () :).

Quelle