¿Forma correcta de leer un archivo de texto en un búfer en C? [duplicate]

6 minutos de lectura

Estoy lidiando con pequeños archivos de texto que quiero leer en un búfer mientras los proceso, así que se me ocurrió el siguiente código:

...
char source[1000000];

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

¿Es esta la forma correcta de poner el contenido del archivo en el búfer o estoy abusando strcat()?

Luego itero a través del búfer así:

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

  • Esto está mal. strcat concatena cadenas. Incluso si &symbol es un char *, no está terminado en nulo. Deberías usar fgets o fread. También, strcat va a ser lento en su caso de todos modos porque escanea source cada vez que necesita agregar un carácter.

    – Alok Singhal

    8 de enero de 2010 a las 16:50


  • Sin mencionar que leer un carácter a la vez será mucho más lento que usar fread.

    – Nick Mayer

    8 de enero de 2010 a las 16:57

  • @Nick: No sé acerca de ‘mucho más lento’: debido al almacenamiento en búfer de io y posiblemente a la inserción de las llamadas a funciones, el impacto en el rendimiento no necesariamente debe ser tan grande; utilizando fread() aunque sigue siendo una buena idea

    – Cristóbal

    8 de enero de 2010 a las 17:07

  • por cierto: no consideraría un archivo de texto de 1m ‘pequeño’;)

    – Cristóbal

    8 de enero de 2010 a las 17:09

  • Busque en mmap() para mapear en memoria el archivo. Tenga cuidado con los desbordamientos de búfer. No use strcat(): incluso si soluciona los problemas con la terminación nula, le da un comportamiento cuadrático que es malo en los archivos de megabytes y un desastre en los archivos de gigabytes.

    –Jonathan Leffler

    8 de enero de 2010 a las 17:45

avatar de usuario
Miguel

char source[1000000];

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

Hay bastantes cosas mal con este código:

  1. Es muy lento (está extrayendo el búfer un carácter a la vez).
  2. Si el tamaño del archivo ha terminado sizeof(source)esto es propenso a desbordamientos de búfer.
  3. Realmente, cuando lo miras más de cerca, este código no debería funcionar en absoluto. Como se indica en las páginas man:

los strcat() agrega una copia de la cadena terminada en nulo s2 al final de la cadena terminada en nulo s1, luego agrega un ‘\0’ de terminación.

Está agregando un carácter (¡no una cadena terminada en NUL!) a una cadena que puede o no terminar en NUL. los solamente El momento en que puedo imaginar que esto funcione de acuerdo con la descripción de la página de manual es si cada carácter en el archivo termina en NUL, en cuyo caso esto sería bastante inútil. Así que sí, esto es definitivamente un terrible abuso de strcat().

Las siguientes son dos alternativas para considerar usar en su lugar.

Si conoce el tamaño máximo del búfer con anticipación:

#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);
}

O, si no lo hace:

#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! */

  • Probablemente también desee anular la terminación de su búfer. En su segundo ejemplo de código, dejó espacio para el nulo, pero en realidad no lo configuró; en tu primera, te olvidaste de dejar espacio para el nulo.

    –Brian Campbell

    8 de enero de 2010 a las 17:10

  • +1 por el uso de ftell y malloc. Este es el camino a seguir.

    – cigarro

    8 de enero de 2010 a las 17:25

  • @Mark: tienes razón, y estoy seguro de que lo sabes, pero sizeof(int) puede ser 1. @Michael, digamos que leo un carácter ‘A’ en symbol. Luego, &symbol (incluso si symbol es char) es un puntero que apunta a 'A' seguida de datos aleatorios. Si symbol es un inty sizeof(int) > 1luego &symbolcuando se convierte en char *puntos a 'A' seguido por 0 o 0dependiendo de la endianidad de la máquina.

    – Alok Singhal

    8 de enero de 2010 a las 18:49

  • @Alok, Mark: Olvidé que getc() devuelve un int, no un char. Wow, eso es muy sutil.

    – Miguel

    8 de enero de 2010 a las 21:55

  • stackoverflow.com/a/238607/309483: “Si usa ftell, entonces debe abrir el archivo en modo binario. Si lo abre en modo de texto, ftell solo devuelve una “cookie” que solo puede usar fseek”.

    – Janus Troelsen

    18/10/2012 a las 22:16

avatar de usuario
Martín Beckett

¡Sí, probablemente serías arrestado por tu terrible abuso de strcat!

Eche un vistazo a getline(), lee los datos una línea a la vez, pero lo más importante es que puede limitar la cantidad de caracteres que lee, para que no se desborde el búfer.

Strcat es relativamente lento porque tiene que buscar el final en toda la cadena en cada inserción de carácter. Normalmente mantendría un puntero al final actual del almacenamiento de cadenas y lo pasaría a getline como la posición para leer la siguiente línea.

Si está en un sistema Linux, una vez que tenga el descriptor de archivo, puede obtener mucha información sobre el archivo usando fstat()

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

así que podrías tener

#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
}

Esto evita buscar el principio y el final del archivo.

Ver este artículo de JoelOnSoftware por qué no quieres usar strcat.

Mirar miedo por una alternativa. Úselo con 1 para el tamaño cuando esté leyendo bytes o caracteres.

¿Por qué no usas la matriz de caracteres que tienes? Esto debería hacerlo:

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

avatar de usuario
condez

No probado, pero debería funcionar. Y sí, podría implementarse mejor con fread, lo dejaré como ejercicio para el lector.

#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;

avatar de usuario
wprl

Creo que quieres pan:

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

  • Sería genial si pudieras dar alguna explicación sobre de qué se trata la fuente.

    – Adrián

    2 de noviembre de 2017 a las 2:35

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad