Come leggere il contenuto di un file in una stringa in C?


97

Qual è il modo più semplice (meno soggetto a errori, meno righe di codice, comunque tu voglia interpretarlo) per aprire un file in C e leggere il suo contenuto in una stringa (char *, char [], qualunque cosa)?


8
Il "modo più semplice" e il "meno soggetto a errori" sono spesso opposti l'uno dell'altro.
Andy Lester

14
"Il modo più semplice" e "il minimo soggetto a errori" sono in realtà sinonimi nel mio libro. Ad esempio, la risposta in C # è string s = File.ReadAllText(filename);. Come potrebbe essere più semplice e più soggetto a errori?
Mark Lakata

Risposte:


146

Tendo a caricare solo l'intero buffer come un pezzo di memoria grezza in memoria e ad eseguire l'analisi da solo. In questo modo ho il miglior controllo su ciò che fa la libreria standard su più piattaforme.

Questo è uno stub che uso per questo. potresti anche voler controllare i codici di errore per fseek, ftell e fread. (omesso per chiarezza).

char * buffer = 0;
long length;
FILE * f = fopen (filename, "rb");

if (f)
{
  fseek (f, 0, SEEK_END);
  length = ftell (f);
  fseek (f, 0, SEEK_SET);
  buffer = malloc (length);
  if (buffer)
  {
    fread (buffer, 1, length, f);
  }
  fclose (f);
}

if (buffer)
{
  // start to process your data / extract strings here...
}

3
Vorrei anche controllare il valore di ritorno di fread, poiché potrebbe non leggere effettivamente l'intero file a causa di errori e cosa no.
spazio libero

6
come ha detto rmeador, fseek fallirà su file> 4 GB.
KPexEA

6
Vero. Per file di grandi dimensioni questa soluzione fa schifo.
Nils Pipenbrinck

31
Poiché questa è una pagina di destinazione, vorrei sottolineare che freadnon termina a zero la tua stringa. Questo può portare a qualche problema.
ivan-k

18
Come ha detto @Manbroski, il buffer deve essere terminato con "\ 0". Quindi cambierei buffer = malloc (length + 1);e aggiungerei dopo fclose: buffer[length] = '\0';(convalidato da Valgrind)
soywod

26

Un'altra soluzione, purtroppo altamente dipendente dal sistema operativo, è la mappatura della memoria del file. I vantaggi includono generalmente le prestazioni di lettura e un utilizzo ridotto della memoria poiché la visualizzazione delle applicazioni e la cache dei file dei sistemi operativi possono effettivamente condividere la memoria fisica.

Il codice POSIX sarebbe simile a questo:

int fd = open("filename", O_RDONLY);
int len = lseek(fd, 0, SEEK_END);
void *data = mmap(0, len, PROT_READ, MAP_PRIVATE, fd, 0);

Windows d'altra parte è un po 'più complicato e sfortunatamente non ho un compilatore davanti a me da testare, ma la funzionalità è fornita da CreateFileMapping()e MapViewOfFile().


3
Non dimenticare di controllare i valori restituiti da quelle chiamate di sistema!
Toby Speight

3
deve usare off_t invece di int quando si chiama lseek ().
ivan.ukr

1
Nota che se l'obiettivo è quello di catturare stabilmente in memoria il contenuto di un file in un dato momento, questa soluzione dovrebbe essere evitata, a meno che tu non sia certo che il file letto in memoria non verrà modificato da altri processi durante l'intervallo su cui verrà utilizzata la mappa. Vedi questo post per maggiori informazioni.
user001

12

Se "leggi il suo contenuto in una stringa" significa che il file non contiene caratteri con codice 0, puoi anche usare la funzione getdelim (), che accetta un blocco di memoria e lo rialloca se necessario, o alloca semplicemente l'intero buffer per e legge il file al suo interno finché non incontra un delimitatore o una fine del file specificati. Basta passare "\ 0" come delimitatore per leggere l'intero file.

Questa funzione è disponibile nella libreria GNU C, http://www.gnu.org/software/libc/manual/html_mono/libc.html#index-getdelim-994

Il codice di esempio potrebbe sembrare semplice come

char* buffer = NULL;
size_t len;
ssize_t bytes_read = getdelim( &buffer, &len, '\0', fp);
if ( bytes_read != -1) {
  /* Success, now the entire file is in the buffer */

1
L'ho già usato! Funziona molto bene, assumendo che il file che stai leggendo sia testo (non contiene \ 0).
effimero

SIMPATICO! Salva molti problemi durante lo slurping di interi file di testo. Ora, se ci fosse un modo ultra semplice simile di leggere un flusso di file binario fino a EOF senza bisogno di alcun carattere di delimitazione!
anthony

6

Se il file è testo e vuoi ottenere il testo riga per riga, il modo più semplice è usare fgets ().

char buffer[100];
FILE *fp = fopen("filename", "r");                 // do not use "rb"
while (fgets(buffer, sizeof(buffer), fp)) {
... do something
}
fclose(fp);

6

Se stai leggendo file speciali come stdin o pipe, non sarai in grado di usare fstat per ottenere in anticipo la dimensione del file. Inoltre, se stai leggendo un file binario, fgets perderà le informazioni sulla dimensione della stringa a causa dei caratteri "\ 0" incorporati. Il modo migliore per leggere un file è quindi usare read e rialloc:

#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

int main () {
    char buf[4096];
    ssize_t n;
    char *str = NULL;
    size_t len = 0;
    while (n = read(STDIN_FILENO, buf, sizeof buf)) {
        if (n < 0) {
            if (errno == EAGAIN)
                continue;
            perror("read");
            break;
        }
        str = realloc(str, len + n + 1);
        memcpy(str + len, buf, n);
        len += n;
        str[len] = '\0';
    }
    printf("%.*s\n", len, str);
    return 0;
}

1
Questo è O (n ^ 2), dove n è la lunghezza del tuo file. Tutte le soluzioni con più voti positivi di questo sono O (n). Si prega di non utilizzare questa soluzione nella pratica o di utilizzare una versione modificata con crescita moltiplicativa.
Clark Gaebel

2
realloc () può estendere la memoria esistente alla nuova dimensione senza copiare la vecchia memoria in una nuova porzione di memoria più grande. solo se ci sono chiamate intermedie a malloc () sarà necessario spostare la memoria e fare questa soluzione O (n ^ 2). qui, non ci sono chiamate a malloc () che avvengono tra le chiamate a realloc () quindi la soluzione dovrebbe andare bene.
Jake

2
Si potrebbe leggere direttamente nel buffer "str" ​​(con un offset appropriato), senza bisogno di copiare da un "buf" intermedio. Quella tecnica tuttavia che in genere allocherà eccessivamente la memoria necessaria per il contenuto del file. Fai anche attenzione ai file binari, printf non li gestirà correttamente e probabilmente non vorrai comunque stampare binari!
anthony

3

Nota: questa è una modifica della risposta accettata sopra.

Ecco un modo per farlo, completo di controllo degli errori.

Ho aggiunto un controllo delle dimensioni per uscire quando il file era più grande di 1 GiB. L'ho fatto perché il programma inserisce l'intero file in una stringa che potrebbe utilizzare troppa RAM e mandare in crash un computer. Tuttavia, se non ti interessa, puoi semplicemente rimuoverlo dal codice.

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

#define FILE_OK 0
#define FILE_NOT_EXIST 1
#define FILE_TO_LARGE 2
#define FILE_READ_ERROR 3

char * c_read_file(const char * f_name, int * err, size_t * f_size) {
    char * buffer;
    size_t length;
    FILE * f = fopen(f_name, "rb");
    size_t read_length;

    if (f) {
        fseek(f, 0, SEEK_END);
        length = ftell(f);
        fseek(f, 0, SEEK_SET);

        // 1 GiB; best not to load a whole large file in one string
        if (length > 1073741824) {
            *err = FILE_TO_LARGE;

            return NULL;
        }

        buffer = (char *)malloc(length + 1);

        if (length) {
            read_length = fread(buffer, 1, length, f);

            if (length != read_length) {
                 *err = FILE_READ_ERROR;

                 return NULL;
            }
        }

        fclose(f);

        *err = FILE_OK;
        buffer[length] = '\0';
        *f_size = length;
    }
    else {
        *err = FILE_NOT_EXIST;

        return NULL;
    }

    return buffer;
}

E per verificare la presenza di errori:

int err;
size_t f_size;
char * f_data;

f_data = c_read_file("test.txt", &err, &f_size);

if (err) {
    // process error
}

2

Se stai usando glib, puoi usare g_file_get_contents ;

gchar *contents;
GError *err = NULL;

g_file_get_contents ("foo.txt", &contents, NULL, &err);
g_assert ((contents == NULL && err != NULL) || (contents != NULL && err == NULL));
if (err != NULL)
  {
    // Report error to user, and free error
    g_assert (contents == NULL);
    fprintf (stderr, "Unable to read file: %s\n", err->message);
    g_error_free (err);
  }
else
  {
    // Use file contents
    g_assert (contents != NULL);
  }
}

1
// Assumes the file exists and will seg. fault otherwise.
const GLchar *load_shader_source(char *filename) {
  FILE *file = fopen(filename, "r");             // open 
  fseek(file, 0L, SEEK_END);                     // find the end
  size_t size = ftell(file);                     // get the size in bytes
  GLchar *shaderSource = calloc(1, size);        // allocate enough bytes
  rewind(file);                                  // go back to file beginning
  fread(shaderSource, size, sizeof(char), file); // read each char into ourblock
  fclose(file);                                  // close the stream
  return shaderSource;
}

Questa è una soluzione piuttosto rozza perché nulla viene verificato rispetto a null.


Questo sarà solo con file basati su disco. Non funzionerà con named pipe, input standard o flussi di rete.
anthony

Ah, anche perché sono venuto qui! Ma penso che sia necessario null terminare la stringa o restituire la lunghezza che glShaderSourceopzionalmente prende.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

1

Appena modificato dalla risposta accettata sopra.

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

char *readFile(char *filename) {
    FILE *f = fopen(filename, "rt");
    assert(f);
    fseek(f, 0, SEEK_END);
    long length = ftell(f);
    fseek(f, 0, SEEK_SET);
    char *buffer = (char *) malloc(length + 1);
    buffer[length] = '\0';
    fread(buffer, 1, length, f);
    fclose(f);
    return buffer;
}

int main() {
    char *content = readFile("../hello.txt");
    printf("%s", content);
}

Questo non è un codice C. La domanda non è contrassegnata come C ++.
Gerhardh

@ Gerhardh Risposta così rapida alla domanda nove anni fa quando sto modificando! Sebbene la parte della funzione sia C pura, mi dispiace per la mia risposta "Non si esegue su C".
BaiJiFeiLong

Questa antica domanda era elencata all'inizio delle domande attive. Non l'ho cercato.
Gerhardh

Questo codice perde memoria, non dimenticare di liberare la tua memoria malloc'd :)
ericcurtin

0

Aggiungerò la mia versione, basata sulle risposte qui, solo per riferimento. Il mio codice prende in considerazione sizeof (char) e aggiunge alcuni commenti.

// Open the file in read mode.
FILE *file = fopen(file_name, "r");
// Check if there was an error.
if (file == NULL) {
    fprintf(stderr, "Error: Can't open file '%s'.", file_name);
    exit(EXIT_FAILURE);
}
// Get the file length
fseek(file, 0, SEEK_END);
long length = ftell(file);
fseek(file, 0, SEEK_SET);
// Create the string for the file contents.
char *buffer = malloc(sizeof(char) * (length + 1));
buffer[length] = '\0';
// Set the contents of the string.
fread(buffer, sizeof(char), length, file);
// Close the file.
fclose(file);
// Do something with the data.
// ...
// Free the allocated string space.
free(buffer);

0

facile e accurato (supponendo che i contenuti nel file siano inferiori a 10000):

void read_whole_file(char fileName[1000], char buffer[10000])
{
    FILE * file = fopen(fileName, "r");
    if(file == NULL)
    {
        puts("File not found");
        exit(1);
    }
    char  c;
    int idx=0;
    while (fscanf(file , "%c" ,&c) == 1)
    {
        buffer[idx] = c;
        idx++;
    }
    buffer[idx] = 0;
}

Per favore, non allocare tutta la memoria di cui pensi di aver bisogno in anticipo. Questo è un perfetto esempio di cattivo design. Dovresti allocare la memoria in base alle tue esigenze ogni volta che è possibile farlo. Sarebbe un buon design se ti aspetti che il file sia lungo 10.000 byte, il tuo programma non può gestire un file di qualsiasi altra dimensione, e stai comunque controllando la dimensione e l'errore, ma non è quello che sta succedendo qui. Dovresti davvero imparare a codificare C correttamente.
Jack Giffin
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.