Korrekte Methode zum Einlesen einer Textdatei in einen Puffer in C? [duplicate]

Lesezeit: 6 Minuten

Ich habe es mit kleinen Textdateien zu tun, die ich in einen Puffer einlesen möchte, während ich sie verarbeite, also habe ich mir den folgenden Code ausgedacht:

...
char source[1000000];

FILE *fp = fopen("TheFile.txt", "r");
if(fp != NULL)
{
    while((symbol = getc(fp)) != EOF)
    {
        strcat(source, &symbol);
    }
    fclose(fp);
}
...

Ist dies der richtige Weg, den Inhalt der Datei in den Puffer zu legen, oder missbrauche ich strcat()?

Ich iteriere dann so durch den Puffer:

for(int x = 0; (c = source[x]) != '\0'; x++)
{
    //Process chars
}

  • Das ist falsch. strcat verkettet Strings. Selbst wenn &symbol ist ein char *, es ist nicht nullterminiert. Du solltest benutzen fgets oder fread. Ebenfalls, strcat wird in Ihrem Fall sowieso langsam sein, weil es scannt source jedes Mal muss ein Zeichen angehängt werden.

    – Alok Singhal

    8. Januar 2010 um 16:50 Uhr


  • Ganz zu schweigen davon, dass das Lesen eines Zeichens auf einmal viel langsamer ist als das Verwenden fread.

    – Nik Meyer

    8. Januar 2010 um 16:57 Uhr

  • @Nick: Ich weiß nichts über ‘viel langsamer’: Aufgrund der io-Pufferung und möglicherweise des Inlinings der Funktionsaufrufe muss die Auswirkung auf die Leistung nicht unbedingt so groß sein. verwenden fread() ist aber trotzdem eine gute idee

    – Christoph

    8. Januar 2010 um 17:07 Uhr

  • Übrigens: Ich würde eine 1m-Textdatei nicht als “klein” betrachten 😉

    – Christoph

    8. Januar 2010 um 17:09 Uhr

  • Sehen Sie in mmap() nach, um die Datei im Speicher abzubilden. Achten Sie auf Pufferüberläufe. Verwenden Sie nicht strcat() – selbst wenn Sie die Probleme mit Nullterminierung beheben, erhalten Sie ein quadratisches Verhalten, das bei Megabyte-Dateien schlecht und bei Gigabyte-Dateien eine Katastrophe ist.

    – Jonathan Leffler

    8. Januar 2010 um 17:45 Uhr

Benutzer-Avatar
Michael

char source[1000000];

FILE *fp = fopen("TheFile.txt", "r");
if(fp != NULL)
{
    while((symbol = getc(fp)) != EOF)
    {
        strcat(source, &symbol);
    }
    fclose(fp);
}

An diesem Code ist einiges falsch:

  1. Es ist sehr langsam (Sie extrahieren den Puffer Zeichen für Zeichen).
  2. Wenn die Dateigröße überschritten ist sizeof(source)ist dies anfällig für Pufferüberläufe.
  3. Wirklich, wenn Sie es genauer betrachten, sollte dieser Code überhaupt nicht funktionieren. Wie in den Manpages angegeben:

Das strcat() Die Funktion hängt eine Kopie des nullterminierten Strings s2 an das Ende des nullterminierten Strings s1 und fügt dann ein abschließendes `\0′ hinzu.

Sie hängen ein Zeichen (keine NUL-terminierte Zeichenfolge!) An eine Zeichenfolge an, die NUL-terminiert sein kann oder nicht. Das nur Zeitweise kann ich mir vorstellen, dass dies gemäß der Manpage-Beschreibung funktioniert, wenn jedes Zeichen in der Datei NUL-terminiert ist, in diesem Fall wäre dies ziemlich sinnlos. Also ja, das ist definitiv ein schrecklicher Missbrauch von strcat().

Im Folgenden finden Sie zwei Alternativen, die Sie stattdessen verwenden sollten.

Wenn Sie die maximale Puffergröße im Voraus kennen:

#include <stdio.h>
#define MAXBUFLEN 1000000

char source[MAXBUFLEN + 1];
FILE *fp = fopen("foo.txt", "r");
if (fp != NULL) {
    size_t newLen = fread(source, sizeof(char), MAXBUFLEN, fp);
    if ( ferror( fp ) != 0 ) {
        fputs("Error reading file", stderr);
    } else {
        source[newLen++] = '\0'; /* Just to be safe. */
    }

    fclose(fp);
}

Oder, falls nicht:

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

char *source = NULL;
FILE *fp = fopen("foo.txt", "r");
if (fp != NULL) {
    /* Go to the end of the file. */
    if (fseek(fp, 0L, SEEK_END) == 0) {
        /* Get the size of the file. */
        long bufsize = ftell(fp);
        if (bufsize == -1) { /* Error */ }

        /* Allocate our buffer to that size. */
        source = malloc(sizeof(char) * (bufsize + 1));

        /* Go back to the start of the file. */
        if (fseek(fp, 0L, SEEK_SET) != 0) { /* Error */ }

        /* Read the entire file into memory. */
        size_t newLen = fread(source, sizeof(char), bufsize, fp);
        if ( ferror( fp ) != 0 ) {
            fputs("Error reading file", stderr);
        } else {
            source[newLen++] = '\0'; /* Just to be safe. */
        }
    }
    fclose(fp);
}

free(source); /* Don't forget to call free() later! */

  • Sie möchten wahrscheinlich auch Ihren Puffer mit Null terminieren. In Ihrem zweiten Codebeispiel haben Sie Platz für die Null gelassen, sie aber nicht wirklich festgelegt. In Ihrem ersten haben Sie es versäumt, Platz für die Null zu lassen.

    – Brian Campell

    8. Januar 2010 um 17:10 Uhr

  • +1 für die Verwendung von ftell und malloc. Dies ist der richtige Weg.

    – Zigarrenmann

    8. Januar 2010 um 17:25 Uhr

  • @ Mark: du hast recht, und ich bin sicher, du weißt das, aber sizeof(int) kann 1 sein. @Michael, sagen wir, ich lese ein Zeichen ‘A’ ein symbol. Dann, &symbol (selbst wenn symbol ist char) ist ein Zeiger, der auf zeigt 'A' gefolgt von zufälligen Daten. Wenn symbol ist ein intund sizeof(int) > 1dann &symbolwenn konvertiert in char *verweist auf 'A' gefolgt von 0 oder 0abhängig von der Endianness der Maschine.

    – Alok Singhal

    8. Januar 2010 um 18:49 Uhr

  • @Alok, Mark: Ich habe vergessen, dass getc() ein int zurückgibt, kein char. Wow, das ist sehr subtil.

    – Michael

    8. Januar 2010 um 21:55 Uhr

  • stackoverflow.com/a/238607/309483: “Wenn Sie ftell verwenden, müssen Sie die Datei im Binärmodus öffnen. Wenn Sie sie im Textmodus öffnen, gibt ftell nur ein “Cookie” zurück, das nur von fseek verwendet werden kann.”

    – Janus Troelsen

    18. Oktober 2012 um 22:16 Uhr

Benutzer-Avatar
Martin Beckett

Ja – Sie würden wahrscheinlich wegen Ihres schrecklichen Missbrauchs von strcat verhaftet werden!

Schauen Sie sich getline() an, es liest die Daten zeilenweise, aber vor allem kann es die Anzahl der gelesenen Zeichen begrenzen, damit der Puffer nicht überläuft.

Strcat ist relativ langsam, weil es bei jeder Zeicheneinfügung den gesamten String nach dem Ende durchsuchen muss. Normalerweise würden Sie einen Zeiger auf das aktuelle Ende des Zeichenfolgenspeichers behalten und diesen an getline als Position übergeben, an der die nächste Zeile gelesen werden soll.

Wenn Sie sich auf einem Linux-System befinden, können Sie mit fstat() viele Informationen über die Datei erhalten, sobald Sie den Dateideskriptor haben.

http://linux.die.net/man/2/stat

also hast du vielleicht

#include  <unistd.h> 
void main()
{
    struct stat stat;
    int fd;
    //get file descriptor
    fstat(fd, &stat);
    //the size of the file is now in stat.st_size
}

Dadurch wird vermieden, bis zum Anfang und Ende der Datei zu suchen.

Sehen diesen Artikel von JoelOnSoftware warum Sie nicht verwenden möchten strcat.

Ansehen Angst für eine Alternative. Verwenden Sie es mit 1 für die Größe, wenn Sie Bytes oder Zeichen lesen.

Warum verwendest du nicht einfach das Array von Zeichen, das du hast? Das sollte es tun:

   source[i] = getc(fp); 
   i++;

Benutzer-Avatar
Earlz

Nicht getestet, sollte aber funktionieren.. Und ja, es könnte mit fread besser umgesetzt werden, das überlasse ich dem Leser als Übung.

#define DEFAULT_SIZE 100
#define STEP_SIZE 100

char *buffer[DEFAULT_SIZE];
size_t buffer_sz=DEFAULT_SIZE;
size_t i=0;
while(!feof(fp)){
  buffer[i]=fgetc(fp);
  i++;
  if(i>=buffer_sz){
    buffer_sz+=STEP_SIZE;
    void *tmp=buffer;
    buffer=realloc(buffer,buffer_sz);
    if(buffer==null){ free(tmp); exit(1);} //ensure we don't have a memory leak
  }
}
buffer[i]=0;

Benutzer-Avatar
wprl

Ich denke, du willst Angst haben:

http://www.cplusplus.com/reference/clibrary/cstdio/fread/

  • Es wäre toll, wenn Sie etwas näher erläutern könnten, worum es bei der Quelle geht.

    – Adrian

    2. November 2017 um 2:35 Uhr

1372840cookie-checkKorrekte Methode zum Einlesen einer Textdatei in einen Puffer in C? [duplicate]

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

Privacy policy