Frage

Ich habe versucht, eine String-Ersetzungsfunktion in C zu schreiben, die auf a funktioniert char *, die mit zugewiesen wurde malloc().Der Unterschied besteht darin, dass Zeichenfolgen und nicht Zeichen in der Startzeichenfolge gesucht und ersetzt werden.

Dies ist trivial, wenn die Such- und Ersetzungszeichenfolgen gleich lang sind (oder die Ersetzungszeichenfolge kürzer als die Suchzeichenfolge ist), da mir genügend Speicherplatz zugewiesen ist.Wenn ich versuche zu verwenden realloc(), erhalte ich eine Fehlermeldung, die mir mitteilt, dass ich ein Double Free mache – was ich aber nicht sehe, da ich nur verwende realloc().

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 versuche realloc() in einem Fall, in dem die ersetzte Zeichenfolge länger als die ursprüngliche Zeichenfolge ist.(Es funktioniert immer noch irgendwie, es spuckt neben dem Ergebnis nur Fehler aus).

Wenn es hilft, sieht der aufrufende Code so 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");
    }
}
War es hilfreich?

Lösung

Im Allgemeinen sollten Sie dies tun niemals Führen Sie eine Freigabe 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 Zuordnungsfunktionen für einen Benutzerpuffer verwenden können.

Vorausgesetzt, dass Sie innerhalb Ihrer Funktion jetzt keine Neuzuweisung vornehmen können, sollten Sie ihr Verhalten ein wenig ändern, z. B. nur eine Ersetzung durchführen, damit der Benutzer die resultierende maximale Länge der Zeichenfolge berechnen und Ihnen einen ausreichend langen Puffer für diese Funktion zur Verfügung stellen kann Ersatz erfolgen soll.

Dann könnten Sie eine andere Funktion erstellen, um die mehrfachen Ersetzungen durchzuführen, aber Sie müssen den gesamten Platz für die resultierende Zeichenfolge zuweisen und die Benutzereingabezeichenfolge kopieren.Anschließend müssen Sie eine Möglichkeit bereitstellen, 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);

Andere Tipps

Zunächst einmal tut es 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 Speicher ändern, der neu zugewiesen wird.In diesem Fall wird das Argument „string“ ungültig.Selbst wenn Sie es neu zuweisen, verliert die Änderung ihren Gültigkeitsbereich, sobald die Funktion endet.

Um das OP zu beantworten, gibt realloc() einen Zeiger auf den neu zugewiesenen Speicher zurück.Der Rückgabewert muss irgendwo gespeichert werden.Im Allgemeinen würden Sie Folgendes 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 an diese Funktion übergeben wird, nicht ändern.Sie können alles zuweisen, was Sie möchten, aber die Änderung wird am Ende der Funktion den Gültigkeitsbereich verlassen.Im folgenden Block kann „input“ nach Abschluss der Funktion ein ungültiger Zeiger sein oder auch 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, liegt die Verantwortung beim Anrufer, den Zeiger, den er für die Eingabe verwendet hat, nie wieder zu verwenden.Wenn es mit dem Rückgabewert übereinstimmt, haben Sie zwei Zeiger auf dieselbe Stelle und müssen nur free() für einen von ihnen aufrufen.Wenn sie nicht übereinstimmen, zeigt der Eingabezeiger jetzt auf Speicher, der möglicherweise dem Prozess gehört oder nicht.Eine Dereferenzierung könnte einen Segmentierungsfehler verursachen.

Sie könnten einen Doppelzeiger für die Eingabe verwenden, etwa so:

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 jetzt noch ungültig.

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

Nur ein Versuch im Dunkeln, weil ich es noch nicht ausprobiert habe, aber wenn man es neu zuordnet, gibt es den Zeiger ähnlich wie malloc zurück.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);

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

Mich interessiert, dass sich niemand explizit zum Speicherverlust im ursprünglichen Design oder zum Off-by-one-Fehler geäußert hat.Und die Beobachtung des Speicherlecks verrät mir genau, warum Sie den Double-Free-Fehler erhalten (weil Sie, um genau zu sein, denselben Speicher mehrmals freigeben – und zwar nachdem Sie den bereits freigegebenen Speicher mit Füßen getreten haben).

Bevor ich die Analyse durchführe, stimme ich denjenigen zu, die sagen, dass Ihre Benutzeroberfläche nicht gerade herausragend ist.Wenn Sie sich jedoch mit den Speicherverlust-/Trampling-Problemen befasst und die Anforderung „Speicher muss zugewiesen werden“ dokumentiert haben, könnte es „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.Folglich hat realloc() wahrscheinlich den ursprünglichen Speicher freigegeben, und dann übergeben Sie ihm denselben Zeiger erneut und beschweren sich, dass Sie denselben Speicher zweimal freigeben, weil Sie ihm den ursprünglichen Wert erneut übergeben.Dadurch geht nicht nur Speicher verloren, sondern es bedeutet auch, dass Sie weiterhin den ursprünglichen Speicherplatz nutzen – und John Downeys Schuss ins Ungewisse zeigt, dass Sie realloc() missbrauchen, betont aber nicht, wie schwerwiegend Sie dies tun.Außerdem liegt ein Off-by-One-Fehler vor, da Sie nicht genügend Speicherplatz für den NUL-Wert „\0“ zuweisen, der die Zeichenfolge beendet.

Der Speicherverlust tritt auf, weil Sie keinen Mechanismus bereitstellen, um dem Aufrufer den letzten Wert der Zeichenfolge mitzuteilen.Da Sie immer wieder die ursprüngliche Zeichenfolge plus das Leerzeichen danach mit Füßen getreten haben, sieht es so aus, als hätte der Code funktioniert, aber wenn Ihr aufrufender Code den Platz frei gemacht hätte, würde auch er einen Double-Free-Fehler bekommen, oder er könnte einen Core-Dump oder etwas Ähnliches erhalten, weil Die Speichersteuerinformationen sind vollständig verschlüsselt.

Ihr Code schützt auch nicht vor unbegrenztem Wachstum – erwägen Sie, „Noel“ durch „Joyeux Noel“ zu ersetzen.Jedes Mal würden Sie 7 Zeichen hinzufügen, aber im ersetzten Text ein weiteres Noel 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 erscheint;Eine Alternative besteht darin, die Ersetzungszeichenfolge zu überspringen und die Suche danach fortzusetzen.Bei der zweiten Lösung müssen einige nicht triviale Codierungsprobleme gelöst werden.

Mein Vorschlag zur Ü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.Eine ausführliche Diskussion zu Fragen der Speicherverwaltung finden Sie im Buch „Writing Solid Code“ von Steve Maguire.

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

Nun, obwohl es schon eine Weile her ist, seit ich C/C++ verwendet habe, verwendet „realloc that grows“ den Speicherzeigerwert nur dann wieder, wenn nach Ihrem ursprünglichen Block noch Platz im Speicher vorhanden ist.

Bedenken Sie zum Beispiel Folgendes:

(xxxxxxxxx.........)

Wenn Ihr Zeiger auf das erste x zeigt und .bedeutet, dass der Speicherplatz frei ist und Sie die Speichergröße, auf die Ihre Variable zeigt, um 5 Bytes vergrößern, dann wird es gelingen.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, ihn um weitere 10 Bytes zu vergrößern, und nur 5 verfügbar sind, muss der Block im Speicher verschoben und Ihr Zeiger aktualisiert werden.

In Ihrem Beispiel übergeben Sie der Funktion jedoch einen Zeiger auf das Zeichen und keinen Zeiger auf Ihre Variable. Daher ist die Funktion strrep zwar möglicherweise intern in der Lage, die verwendete Variable anzupassen, es handelt sich jedoch um eine lokale Variable für die Funktion strrep und Ihr aufrufender Code behält den ursprünglichen Zeigervariablenwert bei.

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 aus Eingang Variable ist tatsächlich eine Eingabe, 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 etwas zu ändern Eingang, da es schwierig sein kann, solche Nebenwirkungen aufzuspüren.

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 Möglichkeit, eine Postleitzahl zu posten, ohne dass es scheiße ist?

realloc ist seltsam, kompliziert und sollte nur verwendet werden, wenn viele Male pro Sekunde mit viel Speicher gearbeitet wird.d.h.- wo es Ihren Code tatsächlich schneller macht.

Ich habe Code wo gesehen

realloc(bytes, smallerSize);

wurde verwendet und daran gearbeitet, die Größe des Puffers zu ändern und ihn zu verkleinern.Hat ungefähr eine Million Mal funktioniert, dann hat realloc aus irgendeinem Grund beschlossen, dass man selbst bei einer Verkürzung des Puffers eine schöne neue Kopie erhalten würde.Sie stürzen also eine halbe Sekunde nach dem Vorfall an einer zufälligen Stelle ab.

Verwenden Sie immer den Rückgabewert von realloc.

Meine kurzen Hinweise.

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

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

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

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top