C-Programmierung: Wie man den gesamten Dateiinhalt in einen Puffer einliest [duplicate]

Lesezeit: 8 Minuten

Ich möchte den gesamten Inhalt einer Datei in einen Puffer schreiben. Die Datei enthält eigentlich nur einen String, den ich mit einem String vergleichen muss.

Was wäre die effizienteste Option, die auch unter Linux portabel ist.

ENV: Windows

  • “das sogar unter Linux portabel ist” sollte “das sogar unter Windows portabel ist” sein …

    Benutzer529758

    22. Dezember 2012 um 12:47 Uhr

  • Ich schreibe Code unter Windows und möchte auch auf Linux portieren

    – Sonnig

    22. Dezember 2012 um 12:48 Uhr

  • Also, was hast du versucht?

    – Mats Petersson

    22. Dezember 2012 um 12:49 Uhr

  • Sie müssen die Datei nicht in einen Puffer einlesen, um sie mit einer Zeichenfolge zu vergleichen. Es ist besser, es im laufenden Betrieb zu tun. 2. Achten Sie auf Codierungen. Unter Windows gibt es einige lächerliche beliebte Kodierungen, wie z. B. UTF-16.

    – Pavel Radzivilovsky

    22. Dezember 2012 um 12:57 Uhr

Die Portierbarkeit zwischen Linux und Windows ist ein großes Problem, da Linux ein POSIX-konformes System mit – im Allgemeinen – einer richtigen, hochwertigen Toolchain für C ist, während Windows nicht einmal viele Funktionen in der C-Standardbibliothek bereitstellt.

Wenn Sie sich jedoch an den Standard halten möchten, können Sie Folgendes schreiben:

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

FILE *f = fopen("textfile.txt", "rb");
fseek(f, 0, SEEK_END);
long fsize = ftell(f);
fseek(f, 0, SEEK_SET);  /* same as rewind(f); */

char *string = malloc(fsize + 1);
fread(string, fsize, 1, f);
fclose(f);

string[fsize] = 0;

Hier string enthält den Inhalt der Textdatei als ordnungsgemäß mit 0 abgeschlossenen C-String. Dieser Code ist nur Standard-C, er ist nicht POSIX-spezifisch (obwohl er nicht garantiert, dass er unter Windows funktioniert/kompiliert wird …)

  • Nur für den Fall, dass sich Besucher fragen, rewind(f); ist äquivalent zu fseek(f, 0, SEEK_SET); und könnte stattdessen hier verwendet werden. Beides gehört dazu <stdio.h>

    – Lynchen

    25. April 2013 um 11:45 Uhr


  • Ach ja, und bevor es darauf ankommt: man muss immer den Rückgabewert prüfen malloc() und fread(). Hier wird die Fehlerprüfung nur der Einfachheit halber weggelassen – kopieren Sie diesen Code nicht wörtlich in eine Produktionscodebasis.

    Benutzer529758

    26. September 2013 um 10:23 Uhr

  • Vergiss es nicht free() das string.

    – Tomáš Zato – Wiedereinsetzung von Monica

    13. Mai 2014 um 12:08 Uhr

  • Tatsächlich tut diese Lösung nicht halte dich an den Standard; Der Standard schreibt vor, dass “ein Binärstrom keine sinnvolle Unterstützung braucht fseek Aufrufe mit einem where-Wert von SEEK_END“, und dass “die Dateipositionsanzeige auf das Dateiende setzen, wie bei fseek(file, 0, SEEK_END)hat ein undefiniertes Verhalten für einen binären Stream”.

    – Ori

    5. März 2016 um 0:46 Uhr

  • Vielleicht fsize = fread(string, 1, fsize, f); wäre besser – falls es nicht ganz gelesen ist.

    – android.weasel

    26. April 2016 um 14:23 Uhr

Hier ist, was ich empfehlen würde.

Es sollte C89-konform und vollständig portabel sein. Insbesondere funktioniert es auch auf Pipes und Sockets auf POSIXy-Systemen.

Die Idee ist, dass wir die Eingabe in großen Stücken lesen (READALL_CHUNK), den Puffer dynamisch nach Bedarf neu zuordnen. Wir verwenden nur realloc(), fread(), ferror()und free():

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

/* Size of each input chunk to be
   read and allocate for. */
#ifndef  READALL_CHUNK
#define  READALL_CHUNK  262144
#endif

#define  READALL_OK          0  /* Success */
#define  READALL_INVALID    -1  /* Invalid parameters */
#define  READALL_ERROR      -2  /* Stream error */
#define  READALL_TOOMUCH    -3  /* Too much input */
#define  READALL_NOMEM      -4  /* Out of memory */

/* This function returns one of the READALL_ constants above.
   If the return value is zero == READALL_OK, then:
     (*dataptr) points to a dynamically allocated buffer, with
     (*sizeptr) chars read from the file.
     The buffer is allocated for one extra char, which is NUL,
     and automatically appended after the data.
   Initial values of (*dataptr) and (*sizeptr) are ignored.
*/
int readall(FILE *in, char **dataptr, size_t *sizeptr)
{
    char  *data = NULL, *temp;
    size_t size = 0;
    size_t used = 0;
    size_t n;

    /* None of the parameters can be NULL. */
    if (in == NULL || dataptr == NULL || sizeptr == NULL)
        return READALL_INVALID;

    /* A read error already occurred? */
    if (ferror(in))
        return READALL_ERROR;

    while (1) {

        if (used + READALL_CHUNK + 1 > size) {
            size = used + READALL_CHUNK + 1;

            /* Overflow check. Some ANSI C compilers
               may optimize this away, though. */
            if (size <= used) {
                free(data);
                return READALL_TOOMUCH;
            }

            temp = realloc(data, size);
            if (temp == NULL) {
                free(data);
                return READALL_NOMEM;
            }
            data = temp;
        }

        n = fread(data + used, 1, READALL_CHUNK, in);
        if (n == 0)
            break;

        used += n;
    }

    if (ferror(in)) {
        free(data);
        return READALL_ERROR;
    }

    temp = realloc(data, used + 1);
    if (temp == NULL) {
        free(data);
        return READALL_NOMEM;
    }
    data = temp;
    data[used] = '\0';

    *dataptr = data;
    *sizeptr = used;

    return READALL_OK;
}

Oben habe ich eine konstante Chunk-Größe verwendet, READALL_CHUNK == 262144 (256*1024). Das bedeutet, dass im schlimmsten Fall bis zu 262145 Zeichen verschwendet werden (allokiert, aber nicht verwendet), aber nur vorübergehend. Am Ende weist die Funktion den Puffer auf die optimale Größe neu zu. Das bedeutet auch, dass wir vier Neuzuweisungen pro Megabyte gelesener Daten vornehmen.

Der Standardwert von 262144 Byte im obigen Code ist ein konservativer Wert; Es funktioniert gut für sogar alte Minilaptops und Raspberry Pis und die meisten eingebetteten Geräte mit mindestens einigen Megabyte RAM, die für den Prozess verfügbar sind. Es ist jedoch nicht so klein, dass es die Operation (aufgrund vieler Leseaufrufe und vieler Pufferneuzuweisungen) auf den meisten Systemen verlangsamt.

Für Desktop-Rechner zu diesem Zeitpunkt (2017) empfehle ich einen viel größeren READALL_CHUNKvielleicht #define READALL_CHUNK 2097152 (2 MiB).

Denn die Definition von READALL_CHUNK geschützt ist (d. h. es ist nur definiert, wenn es zu diesem Zeitpunkt im Code noch undefiniert ist), können Sie den Standardwert zur Kompilierzeit überschreiben, indem Sie (in den meisten C-Compilern) verwenden -DREADALL_CHUNK=2097152 Befehlszeilenoption — aber überprüfen Sie Ihre Compileroptionen für die Definition eines Präprozessormakros mit Befehlszeilenoptionen.

  • Stimmen Sie zu, dass Sie die Datei nicht gesucht haben

    – Elvis Strazdins

    25. April 2018 um 14:59 Uhr

  • @ElvissStrazdins: Danke; Das ist genau der Grund, warum ich erwähnt habe, dass dieser für Rohre und Steckdosen funktioniert – sie sind überhaupt nicht suchbar. Haben Sie eine Meinung, ob ich einen Absatz darüber hinzufügen sollte, wie der Seek-Ansatz bei diesen nicht funktioniert? (Die auch nicht fstat() übrigens.) Das Lesen des Streams, bis das Lesen fehlschlägt, ist wirklich die einzige portable Option, die mit allem funktioniert, was Sie bekommen können FILE anfassen. Ich würde es vorziehen, wenn neue C-Programmierer das wissen, bevor es sie in den Knöchel beißt, wissen Sie.

    – Nominelles Tier

    13. August 2018 um 14:30 Uhr

  • Nicht nur Pipes und Sockets sind das Problem, sondern das Abrufen der Dateigröße mit fstat und das anschließende Lesen der Datei führt zu einer Race-Condition, falls die Datei extern geändert wird (Daten in einem anderen Prozess hinzufügen oder daraus löschen).

    – Elvis Strazdins

    14. August 2018 um 0:57 Uhr

  • @ElvissStrazdins: Sehr wahr. Doch fast alle Antworten auf diese und ähnliche Fragen verwenden die Seek-Methode. Ebenso sollte man verwenden nftw()/fts_..()/glob()/wordexp() statt opendir()/readdir()/closedir(), um Dateien/Verzeichnisse, die während des Durchlaufs hinzugefügt/gelöscht/umbenannt werden, einfach zu handhaben. Ich weiß, dass es mir egal sein sollte, aber ich mag die Idee wirklich nicht, dass mehr C-Programmierer Code schreiben, der nur unter bestimmten Umständen funktioniert und ansonsten stillschweigend fehlschlägt – oder noch schlimmer, Daten zerstört. Die Welt ist bereits voll von solchem ​​Code, und wir brauchen weniger davon, nicht mehr.

    – Nominelles Tier

    14. August 2018 um 1:31 Uhr

  • @Andreas: Der Overhead eines realloc()- und eines read()-Systemaufrufs ist für größere Chunk-Größen (2 MiB oder mehr auf derzeit typischen Desktop-Computern) unbedeutend, sodass die Operation E / A-gebunden ist und die Zeitkomplexität irrelevant ist. die benötigte Zeit ist im Wesentlichen eine lineare Funktion der (großen) Dateigröße. Es ist besser, stattdessen die Menge des zugewiesenen, aber ungenutzten Speichers zu begrenzen.

    – Nominelles Tier

    13. September 2018 um 16:10 Uhr


Eine tragbare Lösung könnte verwendet werden getc.

#include <stdio.h>

char buffer[MAX_FILE_SIZE];
size_t i;

for (i = 0; i < MAX_FILE_SIZE; ++i)
{
    int c = getc(fp);

    if (c == EOF)
    {
        buffer[i] = 0x00;
        break;
    }

    buffer[i] = c;
}

Wenn Sie keine haben möchten MAX_FILE_SIZE Makro oder wenn es sich um eine große Zahl handelt (z buffer wäre zu groß, um auf den Stapel zu passen), verwenden Sie die dynamische Zuordnung.

  • Ordnen Sie dem Heap besser riesige Speicherblöcke zu. Bitte lesen Sie auch nicht Byte für Byte, ich bin mir sicher, dass in jeder anständigen Implementierung von libc die fread() Funktion bietet etwas Effizienteres.

    Benutzer529758

    22. Dezember 2012 um 13:21 Uhr

  • @H2CO3: Ich stimme der Tatsache vollkommen zu, dass das Lesen von Byte pro Byte ineffizient ist, es ging nur darum, eine standardmäßige und sehr einfache Lösung bereitzustellen (fgets könnte auch klappen). Außerdem verwende ich keine POSIX-Funktionen wie z fread unter Windows, da die POSIX-Implementierung durch dieses Betriebssystem oft von den Spezifikationen abweicht. Über die Heap-Zuweisung steht an diesem Ende meiner Antwort.

    – md5

    22. Dezember 2012 um 13:25 Uhr


  • fread() ist nicht POSIX-spezifisch. Wenn Sie es nicht verwenden möchten, können Sie es aufgeben fgetc() auch.

    Benutzer529758

    22. Dezember 2012 um 13:29 Uhr

1421250cookie-checkC-Programmierung: Wie man den gesamten Dateiinhalt in einen Puffer einliest [duplicate]

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

Privacy policy