C编程:如何将整个文件内容读入缓冲区

Sun*_*nny 75 c file-io

我想将文件的全部内容写入缓冲区.该文件实际上只包含一个我需要与字符串进行比较的字符串.

什么是最有效的选项,即使在Linux上也可以移植.

ENV:Windows

小智 140

Linux和Windows之间的可移植性是一个令人头痛的问题,因为Linux是一个符合POSIX标准的系统,通常是一个适当的,高质量的C工具链,而Windows甚至不提供C标准库中的许多功能.

但是,如果你想坚持标准,你可以这样写:

#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, 1, fsize, f);
fclose(f);

string[fsize] = 0;
Run Code Online (Sandbox Code Playgroud)

这里string将包含文本文件的内容作为正确的0终止的C字符串.这段代码只是标准的C,它不是特定于POSIX的(尽管它不保证它可以在Windows上工作/编译......)

  • 为了防止任何访问者想知道,`rewind(f);`相当于`fseek(f,0,SEEK_SET);`而且可以在这里使用.两者都是`<stdio.h>`的一部分 (11认同)
  • 不要忘记`free()``string`. (9认同)
  • 哦,在指出它之前:必须始终检查`malloc()`和`fread()`的返回值.这里,为简单起见,省略了错误检查 - 不要将此代码逐字地复制到生产代码库中. (8认同)
  • 实际上,这个解决方案确实不符合标准; 标准规定"二进制流不需要有意义地支持`fseek`调用,其值为'SEEK_END`",并且"将文件位置指示符设置为文件结尾,如同`fseek(文件,0, SEEK_END)`,具有二进制流的未定义行为". (8认同)
  • 也许fsize = fread(string,1,fsize,f); 会更好 - 如果它没有完全阅读. (4认同)
  • 总是,[ALWAYS](http://stackoverflow.com/questions/19260209/ftell-returning-incorrect-value)以模式`rb`而不是`r`打开文件. (2认同)
  • 几点。1/ 使用 fseek() 或 rewind(0 表示您只能读取磁盘文件。您将无法从以下位置读取文件:标准输入、命名管道、设备或网络流。2/ 小心二进制文件,无论是意外或恶意。 (2认同)

Nom*_*mal 18

这是我推荐的.

它应该符合C89,并且完全可移植.特别是,它也适用于POSIXy系统上的管道和插座.

我们的想法是,我们以大容量chunks(READALL_CHUNK)读取输入,根据需要动态地重新分配缓冲区.我们只使用realloc(),fread(),ferror(),和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;
}
Run Code Online (Sandbox Code Playgroud)

上面,我使用了一个恒定的块大小,READALL_CHUNK== 262144(256*1024).这意味着在最坏的情况下,浪费了多达262145个字符(已分配但未使用),但只是暂时的.最后,该函数将缓冲区重新分配到最佳大小.此外,这意味着我们每兆字节读取数据会进行四次重新分配.

上面代码中的262144字节默认值是保守值; 它适用于老式的minilaptops和Raspberry Pis以及大多数嵌入式设备,至少有几兆字节的RAM可用于此过程.然而,它并不是那么小,以至于它在大多数系统上减慢了操作(由于许多读取调用和许多缓冲区重新分配).

对于目前(2017年)的台式机,我建议更大READALL_CHUNK,也许#define READALL_CHUNK 2097152(2 MiB).

因为保护定义READALL_CHUNK(即,只有在代码中的那一点仍然未定义时才定义),您可以在编译时覆盖默认值,通过使用(在大多数C编译器中)-DREADALL_CHUNK=2097152命令行选项 -但请检查您的编译器选项,以使用命令行选项定义预处理器宏.

  • Upvote不寻找文件 (3认同)
  • @ElvissStrazdins:谢谢;这就是为什么我提到了这个适用于管道和插座的原因-根本找不到它们。您是否有意见我是否应该添加一段有关寻求方法如何不适用于这些问题的段落?(顺便说一句,“ fstat()”方法也不是。)读取流直到读取失败,实际上是唯一可移植的选项,该选项适用于可以打开“ FILE”句柄的所有内容。我希望新的C程序员知道在脚踝被咬之前,您会明白的。 (2认同)
  • 不仅管道和套接字是问题,而且使用 fstat 获取文件大小然后读取文件会产生竞争条件,以防文件被外部修改(在另一个进程中添加或删除其中的数据)。 (2认同)
  • @ElvissStrazdins:非常正确。但是,几乎所有关于此问题的答案和类似问题都使用了seek方法。同样,应该使用`nftw()`/`fts _ ..()`/`glob()`/`wordexp()`而不是`opendir()`/`readdir()`/`closedir()`,轻松处理遍历过程中添加/删除/重命名的文件/目录。我知道我不在乎,但我真的不喜欢让更多的C程序员编写仅在特定情况下有效的代码,而默默地失败-甚至更糟的是破坏数据-的想法。世界上已经充满了这样的代码,我们需要的更少,而不是更多。 (2认同)
  • @Andreas:realloc() 和 read() 系统调用的开销对于较大的块大小(在当前典型的台式机上为 2 MiB 或更大)来说是微不足道的,因此操作受 I/O 限制,时间复杂度无关紧要;花费的时间本质上是(大)文件大小的线性函数。最好限制已分配但未使用的内存量。 (2认同)