getline() vs. fgets(): Steuert die Speicherzuweisung

Lesezeit: 10 Minuten

Um Zeilen aus einer Datei zu lesen, gibt es die getline() und fgets() POSIX-Funktionen (ignoriert die gefürchteten gets()). Das ist gesunder Menschenverstand getline() gegenüber bevorzugt wird fgets() weil es den Zeilenpuffer nach Bedarf zuweist.

Meine Frage: Ist das nicht gefährlich? Was passiert, wenn jemand aus Versehen oder böswilliger Absicht eine 100-GB-Datei ohne Nr '\n' Byte darin – macht das nicht mein getline() Rufen Sie an, eine wahnsinnige Menge an Speicher zuzuweisen?

  • Hängt das nicht von der Umsetzung ab?

    – Martin Veronneau

    3. Mai 2019 um 13:03 Uhr

  • Sicher. Meine Befürchtung ist, dass mein System dann bis zum Stillstand wechseln könnte. Mit fgets() das wäre nicht möglich. Ich frage mich, ob ich etwas falsch verstehe oder ob dieses Risiko besteht getline() ist bekannt.

    – Edavid

    3. Mai 2019 um 13:06 Uhr

  • an insane amount of memory – Sind 100 GB eine wahnsinnige Menge an Speicher? sind 100 KB? ist 1 PB? Sagen Sie es jemandem in 20 Jahren…. Welche Speichermenge ist verrückt und welche nicht? getline() ist eine Funktion aus den 1990er Jahren. Es existiert, um “das Leben einfacher zu machen”, nicht um “alle verrückten Fälle zu behandeln, die ein Benutzer will”. Schreiben ein getline() Die Implementierung mit Max Limit ist nicht so schwer.

    – KamilCuk

    3. Mai 2019 um 14:41 Uhr

  • @KamilCuk Ja, nein bzw. ja. In 20 Jahren werden die Antworten nein, nein und ja lauten. Die genaue Schwelle für das, was eine wahnsinnige Menge an Speicher darstellt, wird in späteren Jahren sicherlich steigen, aber das zugrunde liegende Problem (dass es Zeilen gibt, die zu einem Speichermangel oder Thrashing führen) wird auf jedem endlichen Speichersystem bestehen.

    – Strahl

    3. Mai 2019 um 14:50 Uhr


  • @KamilCuk: Die Vorstellung einer Datei, die einen 100-GB-Datensatz ohne Längenpräfix enthält, scheint ein bisschen verrückt zu sein, egal wie viel Speicher der Zielcomputer hat.

    – Superkatze

    3. Mai 2019 um 15:42 Uhr

Meine Frage: Ist das nicht gefährlich? Was ist, wenn jemand aus Versehen oder böswilliger Absicht eine 100-GB-Datei ohne ‘\n’-Byte erstellt – wird das nicht dazu führen, dass mein Aufruf von getline() eine wahnsinnige Menge an Speicher zuweist?

Ja, was Sie beschreiben, ist ein plausibles Risiko. Jedoch,

  • Wenn das Programm erfordert, dass eine ganze Zeile auf einmal in den Speicher geladen wird, dann erlauben getline() Der Versuch, dies zu tun, ist nicht von Natur aus riskanter, als dafür eigenen Code zu schreiben fgets(); und
  • Wenn Sie ein Programm mit einer solchen Schwachstelle haben, können Sie das Risiko verringern, indem Sie verwenden setrlimit() um die Gesamtmenge an (virtuellem) Speicher zu begrenzen, die es reservieren kann. Dies kann verwendet werden, um einen Fehler zu verursachen, anstatt erfolgreich genug Speicher zuzuweisen, um den Rest des Systems zu stören.

Insgesamt würde ich argumentieren, dass es am besten ist, Code zu schreiben, der überhaupt keine Eingabe in Einheiten von ganzen Zeilen (alle auf einmal) erfordert, aber ein solcher Ansatz hat seine eigenen Komplexitäten.

  • setrlimit könnte funktionieren, aber es gibt Fallstricke; Es kann den residenten Speicher (RSS) auf modernen Kerneln nicht begrenzen, und der virtuelle Speicher (VSZ) kann und wird die physische RAM-Größe sicher überschreiten, lange bevor er anfängt zu thrashen, so dass eine Begrenzung dazu führen kann, dass Zuweisungen fehlschlagen, wenn Sie eher sie sind nicht tun. (stackoverflow.com/questions/39755928/…), (superuser.com/questions/618687/…)

    – Strahl

    3. Mai 2019 um 15:06 Uhr

  • @Ray, wie für setrlimit(), habe ich ausdrücklich vorgeschlagen, die Menge des für den Prozess zulässigen virtuellen Speichers zu begrenzen, nicht dessen RSS. Die Frage, welche Grenze zu wählen ist, ist nebensächlich und wird nicht angesprochen.

    – Johannes Bollinger

    3. Mai 2019 um 15:20 Uhr

  • Fairer Punkt auf fgets. Ich habe diesen Einwand entfernt. Aber das setrlimit-Problem ist immer noch da, selbst wenn der virtuelle Speicher begrenzt wird; RSS ist das, was wir eigentlich tun wollen zu begrenzen, um Thrashing zu vermeiden, und VSZ ist a lose Obergrenze davon. Zum Beispiel habe ich eine Kopie von gvim ausgeführt, die 5 MB tatsächlichen Speicher, aber 550 MB VSZ verwendet. Die Verwendung von setrlimit zum Begrenzen des virtuellen Speichers auf die tatsächliche RAM-Größe führt dazu, dass Zuweisungen lange vor der Notwendigkeit fehlschlagen. Ich stimme zu, dass setrlimit hier nützlich sein kann, aber dieser Ansatz hat Probleme, die das OP kennen muss, bevor er nützlich ist.

    – Strahl

    3. Mai 2019 um 15:31 Uhr


  • Fair genug, @Ray, aber ich behaupte, dass es für das Programm vernünftig ist, ein VSZ-Limit festzulegen, wenn es überhaupt Sinn macht, dass das Programm ganze Zeilen auf einmal in den Speicher lädt viel kleiner als nötig, um zu vermeiden, dass der Vorgang ausfällt. Das OP postuliert, dass die von ihm beschriebene Eingabe schließlich fehlerhaft ist. Wenn das Programm riesige Zeilen verarbeiten muss, dann ist es das Problem, dass sie vollständig im Speicher gehalten werden müssen.

    – Johannes Bollinger

    3. Mai 2019 um 16:01 Uhr


Es kann gefährlich sein, ja. Ich weiß nicht, wie das auf anderen Computern funktionieren würde, aber das Ausführen des folgenden Codes hat meinen Computer bis zu dem Punkt eingefroren, an dem ein Hard-Reset erforderlich war:

/* DANGEROUS CODE */

#include <stdio.h>

int main(void)
{
    FILE *f;
    char *s;
    size_t n = 0;

    f = fopen("/dev/zero", "r");
    getline(&s, &n, f);

    return 0;
}

Einige Codierungsrichtlinien (wie MISRA C) können Sie daran hindern, dynamische Speicherzuweisungen (wie z getline()). Dafür gibt es Gründe, zum Beispiel die Vermeidung von Speicherlecks.

Wenn Sie die maximale Größe aller akzeptablen Zeilen kennen, können Sie Speicherzuweisungen vermeiden, indem Sie verwenden fgets() Anstatt von getline()und entfernen Sie so einen potenziellen Speicherleckpunkt.

Das getline Funktion verwendet malloc und realloc intern und gibt -1 zurück, wenn sie fehlschlagen, also ist das Ergebnis nicht anders als wenn Sie versucht hätten anzurufen malloc(100000000000). Nämlich, errno wird eingestellt ENOMEM und getline gibt -1 zurück.

Sie hätten also das gleiche Problem, ob Sie es verwenden getline oder versucht, dasselbe mit zu tun fgets und manuelle Speicherzuweisung, um sicherzustellen, dass Sie eine ganze Zeile lesen.

Es hängt wirklich davon ab, wie Sie mit zu langen Zeilen umgehen möchten.

fgets mit einem anständig großen Puffer funktioniert im Allgemeinen, und Sie können feststellen, dass es “fehlgeschlagen” ist – das Pufferende hat kein Zeilenumbruchzeichen. Es ist möglich zu vermeiden, dass immer ein strlen() ausgeführt wird, um zu bestätigen, ob der Puffer übergelaufen ist, aber das ist eine andere Frage.

Vielleicht besteht Ihre Strategie darin, Zeilen, die nicht verarbeitet werden können, einfach zu überspringen, oder vielleicht ist der Rest der Zeile nur ein Kommentar, den Sie sowieso ignorieren würden, in diesem Fall ist es einfach, ihn dann zu setzen fgets in einer Schleife, um den Rest der Zeile ohne Zuordnungsstrafe zu verwerfen.

Wenn Sie trotzdem die ganze Zeile lesen möchten getline vielleicht die bessere Strategie für Sie. Der böswillige Benutzer würde viel Speicherplatz benötigen, um das von Ihnen beschriebene schlechte Verhalten zu verursachen, oder vielleicht /dev/random oder ähnliches als Eingabedateinamen übergeben.

Nochmals, wenn getline can’t realloc es wird auf eine Weise fehlschlagen, von der Sie sich erholen können. Wenn Sie den Puffer jedoch für mehrere Zeilenlesevorgänge wiederverwenden, möchten Sie möglicherweise den Puffer freigeben, den er nach einem Fehler hat, bevor Sie versuchen, mehr zu lesen ist immer noch zugewiesen und ist möglicherweise so groß geworden, wie es möglich war, bevor es versagt.

  • Ich habe mich jetzt entschieden, eine Zeilenlängenbegrenzung von 10.000 festzulegen und zu verwenden fgets(). Die Begrenzung der Leitungslänge ist in meinem Anwendungsfall vollkommen in Ordnung. Ich habe die ursprüngliche Frage gestellt, weil in der glibc-Dokumentation für fgets() es gibt eine Warnung vor einem Fehler von fgets()kann der Aufrufer nicht erkennen, ob die Eingabedaten NUL-Bytes enthalten, was empfohlen wird getline() stattdessen. Allerdings z getline()die durch unkontrollierte Speicherallokation das System zum Absturz bringen könnten, gibt es keine Warnung.

    – Edavid

    6. Mai 2019 um 9:17 Uhr


  • Hmm … ein Großteil der C-String-API wird besiegt, wenn Ihr String Nullzeichen enthält, aber wenn dies der Fall ist, dann ist es keine Textdatei, und Sie sollten daher überhaupt keine zeilenbasierte Verarbeitung verwenden.

    – Juwel Taylor

    7. Mai 2019 um 9:51 Uhr

  • In Bezug auf das böswillige Potenzial des Einfügens von Nullbytes wird Ihr Code eine verkürzte Zeile sehen und möglicherweise (je nachdem, wie Sie es erkennen) die nächste Zeile dann als Zeilenfortsetzung behandeln. Ich kann nicht sofort sehen, wie das schlimmer wäre als jede andere etwas zwielichtige Textdateieingabe.

    – Juwel Taylor

    7. Mai 2019 um 9:56 Uhr

  • Exakt. Die glibc-Dokumentation sagt jedoch über fgets: „Verwenden Sie es nicht, um Dateien zu lesen, die vom Benutzer bearbeitet wurden, denn wenn der Benutzer ein Nullzeichen einfügt, sollten Sie es entweder richtig behandeln oder eine eindeutige Fehlermeldung ausgeben. Wir empfehlen die Verwendung von getline anstelle von fgets.“ – Ich bin überrascht, dass sie sich sehr um dieses Ärgernis kümmern, aber anscheinend nicht um die Gefahr der unbegrenzten Speicherzuweisung von getline.

    – Edavid

    8. Mai 2019 um 15:58 Uhr

  • Hmm … schlagen sie vielleicht einen Weg vor, wie man richtig damit umgeht? Ich bin unten strlen vs ftell Delta-Fehlanpassung? ftell ist ein ok Speed ​​Call, aber es bedeutet, dass Sie anrufen mussten strlen Tatsächlich verwenden ftell ist im Allgemeinen keine schlechte Strategie, aber Sie müssen sich mit Windows/Unix-Textdateien befassen.

    – Juwel Taylor

    8. Mai 2019 um 16:25 Uhr

Benutzer-Avatar
Martin Veronneau

getline() Weisen Sie den Puffer für Sie neu zu, um die Speicherverwaltung in Ihrem Programm ein wenig zu erleichtern.

Dies könnte jedoch tatsächlich dazu führen, dass ein großer Teil des Speichers zugewiesen wird. Wenn dies ein Problem darstellt, sollten Sie zusätzliche Schritte unternehmen, um Funktionen zu verwenden, die nicht implizit Speicher zuweisen.

  • Ich habe mich jetzt entschieden, eine Zeilenlängenbegrenzung von 10.000 festzulegen und zu verwenden fgets(). Die Begrenzung der Leitungslänge ist in meinem Anwendungsfall vollkommen in Ordnung. Ich habe die ursprüngliche Frage gestellt, weil in der glibc-Dokumentation für fgets() es gibt eine Warnung vor einem Fehler von fgets()kann der Aufrufer nicht erkennen, ob die Eingabedaten NUL-Bytes enthalten, was empfohlen wird getline() stattdessen. Allerdings z getline()die durch unkontrollierte Speicherallokation das System zum Absturz bringen könnten, gibt es keine Warnung.

    – Edavid

    6. Mai 2019 um 9:17 Uhr


  • Hmm … ein Großteil der C-String-API wird besiegt, wenn Ihr String Nullzeichen enthält, aber wenn dies der Fall ist, dann ist es keine Textdatei, und Sie sollten daher überhaupt keine zeilenbasierte Verarbeitung verwenden.

    – Juwel Taylor

    7. Mai 2019 um 9:51 Uhr

  • In Bezug auf das böswillige Potenzial des Einfügens von Nullbytes wird Ihr Code eine verkürzte Zeile sehen und möglicherweise (je nachdem, wie Sie es erkennen) die nächste Zeile dann als Zeilenfortsetzung behandeln. Ich kann nicht sofort sehen, wie das schlimmer wäre als jede andere etwas zwielichtige Textdateieingabe.

    – Juwel Taylor

    7. Mai 2019 um 9:56 Uhr

  • Exakt. Die glibc-Dokumentation sagt jedoch über fgets: „Verwenden Sie es nicht, um Dateien zu lesen, die vom Benutzer bearbeitet wurden, denn wenn der Benutzer ein Nullzeichen einfügt, sollten Sie es entweder richtig behandeln oder eine eindeutige Fehlermeldung ausgeben. Wir empfehlen die Verwendung von getline anstelle von fgets.“ – Ich bin überrascht, dass sie sich sehr um dieses Ärgernis kümmern, aber anscheinend nicht um die Gefahr der unbegrenzten Speicherzuweisung von getline.

    – Edavid

    8. Mai 2019 um 15:58 Uhr

  • Hmm … schlagen sie vielleicht einen Weg vor, wie man richtig damit umgeht? Ich bin unten strlen vs ftell Delta-Fehlanpassung? ftell ist ein ok Speed ​​Call, aber es bedeutet, dass Sie anrufen mussten strlen Tatsächlich verwenden ftell ist im Allgemeinen keine schlechte Strategie, aber Sie müssen sich mit Windows/Unix-Textdateien befassen.

    – Juwel Taylor

    8. Mai 2019 um 16:25 Uhr

1382620cookie-checkgetline() vs. fgets(): Steuert die Speicherzuweisung

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

Privacy policy