Was passiert mit dem Speicher nach ‘\0’ in einem C-String?

Lesezeit: 10 Minuten

Benutzeravatar von Erika Electra
Erika Elektra

Überraschend einfache/dumme/grundlegende Frage, aber ich habe keine Ahnung: Angenommen, ich möchte dem Benutzer meiner Funktion einen C-String zurückgeben, dessen Länge ich am Anfang der Funktion nicht kenne. Bei der Länge kann ich anfangs nur eine Obergrenze setzen, je nach Verarbeitung kann die Größe schrumpfen.

Die Frage ist, ob etwas falsch daran ist, genügend Heap-Speicherplatz (die obere Grenze) zuzuweisen und dann die Zeichenfolge während der Verarbeitung deutlich davor zu beenden? dh wenn ich ein ‘\ 0’ in die Mitte des zugewiesenen Speichers stecke, funktioniert (a.) free() noch richtig funktionieren, und (b.) wird das Leerzeichen nach dem ‘\0’ belanglos? Sobald ‘\0’ hinzugefügt wird, wird der Speicher einfach zurückgegeben, oder sitzt er dort und nimmt Platz in Beschlag, bis free() wird genannt? Ist es im Allgemeinen ein schlechter Programmierstil, diesen hängenden Platz dort zu lassen, um im Voraus Programmierzeit zu sparen, um den erforderlichen Platz zu berechnen, bevor malloc aufgerufen wird?

Nehmen wir an, ich möchte aufeinanderfolgende Duplikate wie folgt entfernen, um dem Kontext einen gewissen Kontext zu geben:

Eingabe “Hallo oOOOo !!” –> Ausgabe “Helo oOo !”

… und etwas Code unten, der zeigt, wie ich die Größe, die sich aus meiner Operation ergibt, vorberechne und die Verarbeitung effektiv zweimal durchführe, um die richtige Heap-Größe zu erhalten.

char* RemoveChains(const char* str)
{
    if (str == NULL) {
        return NULL;
    }
    if (strlen(str) == 0) {
        char* outstr = (char*)malloc(1);
        *outstr="\0";
        return outstr;
    }
    const char* original = str; // for reuse
    char prev = *str++;       // [prev][str][str+1]...
    unsigned int outlen = 1;  // first char auto-counted

    // Determine length necessary by mimicking processing
    while (*str) {
        if (*str != prev) { // new char encountered
            ++outlen;
            prev = *str; // restart chain
        }
        ++str; // step pointer along input
    }

    // Declare new string to be perfect size
    char* outstr = (char*)malloc(outlen + 1);
    outstr[outlen] = '\0';
    outstr[0] = original[0];
    outlen = 1;

    // Construct output
    prev = *original++;
    while (*original) {
        if (*original != prev) {
            outstr[outlen++] = *original;
            prev = *original;
        }
        ++original;
    }
    return outstr;
}

  • Beachten Sie, dass es schlechter Stil ist, Anrufer zu bitten, sie zu verwenden free() um von Funktionen zurückgegebene Objekte freizugeben, da der Aufrufer möglicherweise mit einer anderen C-Bibliothek verknüpft ist, und dies Sie auch daran hindert, in Zukunft einen anderen Zuordner zu verwenden. Sie sollten eine kleine Wrapper-Funktion bereitstellen, um von Ihrer Bibliothek zurückgegebene Zeichenfolgen freizugeben.

    – Simon Richter

    16. April 2012 um 13:14 Uhr

  • Danke für den Tipp, aber ich bin mir nicht sicher, wo der Wrapper aufgerufen werden würde: automatisch beim Beenden des Programms? Ich bin mir nicht sicher, wie ich das erzwingen kann. Wenn es sich nur um einen Wrapper für meine Funktion handelt, wie lasse ich meinen Benutzer die Operationen (an meiner Ausgabezeichenfolge) eingeben, vorausgesetzt, er möchte etwas damit tun, bevor ich die kostenlose Ausführung durchführe? Ist es konventioneller und akzeptierter, void-Funktionen zu schreiben, die einen Ausgabezeiger anfordern, den ich ändern kann? Was ist, wenn ich die Länge eines solchen Zeigers neu zuweisen oder ändern muss? (nebenbei: Ich muss char* oder const char* anstelle von std::string verwenden)

    – Erika Elektra

    17. April 2012 um 6:55 Uhr

  • Warten Sie, ich habe gerade Folgendes gesehen: Scheint, als könnte die Rückgabe eines const char* diesen schlechten Stilpunkt lösen? stackoverflow.com/questions/3323675/…

    – Erika Elektra

    17. April 2012 um 7:14 Uhr


  • Ihre Wrapper-Funktion würde einfach aufrufen free() auf dem Zeiger, aber das ist jetzt ein Implementierungsdetail. Wenn Sie sich ändern RemoveChains() Um eine andere Zuweisungsfunktion zu verwenden, können Sie auch den Wrapper anpassen, und bestehende Programme funktionieren weiterhin.

    – Simon Richter

    17. April 2012 um 11:40 Uhr

  • Sie können die unbekannte Größe zunächst verwalten malloc etwas, das wahrscheinlich in den meisten Fällen ausreicht, aber nicht zu groß ist (z. B. 256 Byte). Dann schreiben Sie in diesen Puffer und verfolgen, wie viel Platz Sie noch haben. Wenn Ihnen der Platz ausgeht, Sie realloc mit der doppelten Größe (zB 512) und weitermachen. Spülen und wiederholen. Die Gesamtzeit, die für die (Neu-)Zuweisung aufgewendet wird, ist am schlechtesten O(n) wo n ist die endgültige Länge und wird es in vielen Fällen auch sein O(log n) seit realloc muss keine Daten kopieren, wenn nach dem Puffer genügend nicht zugeordneter Speicherplatz vorhanden ist. Du kannst realloc am Ende mit der richtigen Größe.

    – Nicu Stiurca

    4. März 2015 um 5:29 Uhr

Benutzeravatar von Tony Delroy
Toni Delroy

Wenn ich ein ‘\0’ in die Mitte des zugewiesenen Speichers stecke, funktioniert es

(a.) free() funktioniert immer noch einwandfrei, und

Ja.

(b.) wird das Leerzeichen nach dem ‘\0’ belanglos? Sobald ‘\0’ hinzugefügt wird, wird der Speicher einfach zurückgegeben, oder sitzt er dort und belegt Platz, bis free() aufgerufen wird?

Beruht. Wenn Sie große Mengen an Heap-Speicherplatz zuweisen, weist das System häufig zuerst virtuellen Adressraum zu – während Sie auf die Seiten schreiben, wird etwas tatsächlicher physischer Speicher zugewiesen (und dieser kann später auf die Festplatte ausgelagert werden, wenn Ihr Betriebssystem über virtuellen Speicher verfügt Unterstützung). Bekanntlich ermöglicht diese Unterscheidung zwischen verschwenderischer Zuweisung von virtuellem Adressraum und tatsächlichem physischem/Auslagerungsspeicher, dass spärliche Arrays auf solchen Betriebssystemen einigermaßen speichereffizient sind.

Nun, die Granularität dieser virtuellen Adressierung und Paging liegt in Speicherseitengrößen – das könnten 4k, 8k, 16k sein …? Die meisten Betriebssysteme haben eine Funktion, die Sie aufrufen können, um die Seitengröße herauszufinden. Wenn Sie also viele kleine Zuweisungen vornehmen, ist das Aufrunden auf Seitengrößen verschwenderisch, und wenn Sie einen begrenzten Adressraum im Verhältnis zu der Menge an Speicher haben, die Sie wirklich verwenden müssen, dann abhängig von der virtuellen Adressierung auf die oben beschriebene Weise wird nicht skaliert (z. B. 4 GB RAM mit 32-Bit-Adressierung). Wenn Sie andererseits einen 64-Bit-Prozess mit beispielsweise 32 GB RAM ausführen und relativ wenige solcher Zeichenfolgenzuweisungen vornehmen, haben Sie eine enorme Menge an virtuellem Adressraum, mit dem Sie spielen können, und das Aufrunden auf die Seitengröße gewinnt. t belaufen sich auf viel.

Beachten Sie jedoch den Unterschied zwischen dem Schreiben im gesamten Puffer und dem anschließenden Beenden zu einem früheren Zeitpunkt (in diesem Fall verfügt der einmal beschriebene Speicher über einen Sicherungsspeicher und könnte im Auslagerungsbereich landen) im Vergleich zu einem großen Puffer, in den Sie immer nur schreiben bis zum ersten Bit und enden dann (in diesem Fall wird Sicherungsspeicher nur für den belegten Speicherplatz aufgerundet auf die Seitengröße zugewiesen).

Es ist auch erwähnenswert, dass auf vielen Betriebssystemen Heap-Speicher möglicherweise nicht an das Betriebssystem zurückgegeben wird, bis der Prozess beendet ist: Stattdessen benachrichtigt die malloc/free-Bibliothek das Betriebssystem, wenn es den Heap vergrößern muss (z sbrk() unter UNIX bzw VirtualAlloc() unter Windows). In diesem Sinne, free() Der Speicher steht Ihrem Prozess zur Wiederverwendung zur Verfügung, jedoch nicht zur Verwendung durch andere Prozesse. Einige Betriebssysteme optimieren dies – zum Beispiel, indem sie einen bestimmten und unabhängig freizugebenden Speicherbereich für sehr große Zuweisungen verwenden.

Ist es im Allgemeinen ein schlechter Programmierstil, diesen hängenden Platz dort zu lassen, um im Voraus Programmierzeit zu sparen, um den erforderlichen Platz zu berechnen, bevor malloc aufgerufen wird?

Auch hier hängt es davon ab, mit wie vielen solchen Zuordnungen Sie es zu tun haben. Wenn es relativ zu Ihrem virtuellen Adressraum / RAM sehr viele gibt, möchten Sie der Speicherbibliothek ausdrücklich mitteilen, dass nicht der gesamte ursprünglich angeforderte Speicher tatsächlich benötigt wird realloc()oder Sie könnten sogar verwenden strdup() einen neuen Block strenger auf der Grundlage des tatsächlichen Bedarfs zuzuweisen (dann free() das Original) – abhängig von Ihrer malloc/free-Bibliotheksimplementierung könnte das besser oder schlechter funktionieren, aber nur sehr wenige Anwendungen wären von einem Unterschied erheblich betroffen.

Manchmal befindet sich Ihr Code möglicherweise in einer Bibliothek, in der Sie nicht erraten können, wie viele Zeichenfolgeninstanzen die aufrufende Anwendung verwalten wird – in solchen Fällen ist es besser, ein langsameres Verhalten bereitzustellen, das nie zu schlimm wird … also tendieren Sie dazu, die Speicherblöcke zu verkleinern Passen Sie die Zeichenfolgendaten an (eine festgelegte Anzahl zusätzlicher Operationen beeinträchtigt also nicht die Big-O-Effizienz), anstatt einen unbekannten Anteil des ursprünglichen Zeichenfolgenpuffers zu verschwenden (in einem pathologischen Fall – null oder ein Zeichen, das nach willkürlich großen Zuweisungen verwendet wird). Als Leistungsoptimierung könnten Sie sich nur die Mühe machen, Speicher zurückzugeben, wenn der nicht verwendete Speicherplatz >= der verwendete Speicherplatz ist – stimmen Sie ihn nach Geschmack ab oder machen Sie ihn vom Anrufer konfigurierbar.

Sie kommentieren eine andere Antwort:

Es kommt also darauf an zu beurteilen, ob der Realloc länger dauert, oder die Größenbestimmung vor der Verarbeitung?

Wenn Leistung Ihre oberste Priorität ist, dann ja – Sie möchten ein Profil erstellen. Wenn Sie nicht an die CPU gebunden sind, nehmen Sie als allgemeine Regel den “Vorverarbeitungs”-Hit und nehmen Sie eine Zuweisung in der richtigen Größe vor – es gibt nur weniger Fragmentierung und Chaos. Um dem entgegenzuwirken, wenn Sie für eine Funktion einen speziellen Vorverarbeitungsmodus schreiben müssen, ist dies eine zusätzliche “Oberfläche” für Fehler und zu wartenden Code. (Diese Kompromissentscheidung ist häufig erforderlich, wenn Sie Ihre eigene implementieren asprintf() aus snprintf()aber da kann man wenigstens vertrauen snprintf() wie dokumentiert zu handeln und es nicht persönlich pflegen zu müssen).

  • Kleine Klarstellung: Obwohl der zusätzliche Speicher vom Betriebssystem möglicherweise nicht als “benutzt” angesehen wird, wenn Ihr Programm noch nicht darauf zugegriffen hat, ist es Wille als „benutzt“ betrachtet werden malloc() und Freunde, bis Sie entweder free() der Block bzw realloc() es mit einer kleineren Größe.

    – Wyzard

    25. April 2012 um 1:28 Uhr

  • Und wenn im gesamten Block bereits Daten gespeichert sind, wie im Fall einer langen Zeichenfolge, die anschließend schrumpft, werden diese Seiten vom Betriebssystem als in Gebrauch betrachtet, sodass sie bei knappem Speicher ausgelagert werden müssen (stattdessen von einfach verworfen), obwohl sich Ihr Programm nicht wirklich um den Inhalt kümmert.

    – Wyzard

    25. April 2012 um 1:29 Uhr

Sobald ‘\0’ hinzugefügt wird, wird der Speicher einfach zurückgegeben, oder sitzt er dort und belegt Platz, bis free() aufgerufen wird?

Daran ist nichts Magisches \0. Du musst anrufen realloc wenn Sie den zugewiesenen Speicher “verkleinern” möchten. Andernfalls bleibt der Speicher einfach dort, bis Sie anrufen free.

Wenn ich ein ‘\0’ in die Mitte des zugewiesenen Speichers stecke, funktioniert (a.) free() immer noch richtig

Was auch immer Sie tun in dieser Erinnerung free funktioniert immer richtig, wenn Sie genau denselben Zeiger übergeben, der von zurückgegeben wurde malloc. Natürlich, wenn Sie außerhalb schreiben, sind alle Wetten ungültig.

  • Danke, ich verstehe. Es kommt also darauf an zu beurteilen, ob der Realloc länger dauert, oder die Größenbestimmung vor der Verarbeitung?

    – Erika Elektra

    16. April 2012 um 8:29 Uhr

\0 ist nur ein weiteres Zeichen von malloc und free Perspektive ist es ihnen egal, welche Daten Sie in den Speicher eingeben. So free wird immer noch funktionieren, ob Sie hinzufügen \0 in der Mitte oder nicht hinzufügen \0 überhaupt. Der zugewiesene zusätzliche Speicherplatz ist immer noch vorhanden und wird nicht an den Prozess zurückgegeben, sobald Sie ihn hinzufügen \0 zur Erinnerung. Ich persönlich würde es vorziehen, nur die erforderliche Menge an Speicher zuzuweisen, anstatt an einer Obergrenze zuzuweisen, da dies nur die Ressource verschwendet.

Sobald Sie Speicher vom Heap erhalten, indem Sie malloc() aufrufen, können Sie den Speicher verwenden. Das Einfügen von \0 ist wie das Einfügen jedes anderen Zeichens. Dieser Speicher bleibt in Ihrem Besitz, bis Sie ihn freigeben oder das Betriebssystem ihn zurückfordert.

Benutzeravatar von Matthias
Mathias

Das \0ist eine reine Konvention, Zeichen-Arrays als Strings zu interpretieren – sie ist unabhängig von der Speicherverwaltung. Dh wer sein Geld zurück haben will, sollte anrufen realloc. Der String kümmert sich nicht um den Speicher (was eine Quelle vieler Sicherheitsprobleme ist).

Benutzeravatar von Anerudhan Gopal
Anerudhan Gopal

malloc weist nur einen Teil des Speichers zu … Es liegt an Ihnen, es zu verwenden, wie Sie möchten, und von der anfänglichen Zeigerposition aus frei aufzurufen … Das Einfügen von ‘\0’ in der Mitte hat keine Konsequenzen …

Um genau zu sein, weiß malloc nicht, welche Art von Speicher Sie wollen (es gibt nur einen void-Zeiger zurück) ..

Angenommen, Sie möchten 10 Byte Speicher beginnend bei 0x10 bis 0x19 zuweisen.

char * ptr = (char *)malloc(sizeof(char) * 10);

Das Einfügen einer Null an der 5. Position (0x14) gibt den Speicher ab 0x15 nicht frei …

Ein Frei von 0x10 gibt jedoch den gesamten Block von 10 Bytes frei.

Benutzeravatar von Alnitak
Alnitak

  1. free() funktioniert immer noch mit einem NUL-Byte im Speicher

  2. der Platz bleibt verschwendet, bis free() aufgerufen wird, oder wenn Sie die Zuordnung nachträglich verkleinern

1415290cookie-checkWas passiert mit dem Speicher nach ‘\0’ in einem C-String?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy