【问题标题】:Correct way to read a text file into a buffer in C? [duplicate]将文本文件读入 C 中的缓冲区的正确方法? [复制]
【发布时间】:2011-01-03 00:00:33
【问题描述】:

我正在处理我想在处理它们时读入缓冲区的小文本文件,所以我想出了以下代码:

...
char source[1000000];

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

这是将文件内容放入缓冲区的正确方法还是我在滥用strcat()

然后我这样遍历缓冲区:

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

【问题讨论】:

  • 这是错误的。 strcat 连接字符串。即使&symbolchar *,它也不是空终止的。您应该使用fgetsfread。此外,strcat 在您的情况下无论如何都会很慢,因为它每次需要附加一个字符时都会扫描source
  • 更不用说一次读取一个字符会比使用fread慢得多。
  • @Nick:我不知道“慢得多”:由于 io 缓冲和可能的函数调用内联,性能影响不一定要那么大;不过,使用fread() 仍然是个好主意
  • 顺便说一句:我不会认为 1m 的文本文件“小”;)
  • 查看 mmap() 以内存映射文件。注意缓冲区溢出。不要使用 strcat() - 即使你解决了空终止的问题,它也会给你带来二次行为,这对兆字节文件是不好的,对千兆字节文件是灾难。

标签: c input buffer


【解决方案1】:
char source[1000000];

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

这段代码有很多问题:

  1. 它非常慢(您一次提取一个字符的缓冲区)。
  2. 如果文件大小超过sizeof(source),则容易出现缓冲区溢出。
  3. 确实,当您仔细观察时,这段代码根本不应该工作。如手册页所述:

strcat() 函数将一个以空字符结尾的字符串 s2 的副本附加到以空字符结尾的字符串 s1 的末尾,然后添加一个结尾的 `\0'。

您将一个字符(不是以 NUL 结尾的字符串!)附加到一个可能以也可能不以 NUL 结尾的字符串。 唯一时间我可以根据手册页的描述想象这个工作是如果文件中的每个字符都是 NUL 终止的,在这种情况下这将毫无意义。所以是的,这绝对是对strcat() 的严重滥用。

以下是可以考虑使用的两种替代方法。

如果您提前知道最大缓冲区大小:

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

或者,如果您不这样做:

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

【讨论】:

  • 您可能还想对缓冲区进行空终止。在您的第二个代码示例中,您为空值留出了空间,但实际上并没有设置它;在你的第一个,你忽略了为空值留出空间。
  • @Mark:你是对的,我相信你知道这一点,但 sizeof(int) 可以是 1。@Michael,假设我在 symbol 中读到一个字符“A”。然后,&amp;symbol(即使symbolchar)是指向'A' 的指针,后跟随机数据。如果symbolintsizeof(int) &gt; 1,则&amp;symbol 在转换为char * 时指向'A',后跟00,具体取决于机器的字节序。
  • @Alok,Mark:我忘了 getc() 返回的是 int,而不是 char。哇,这很微妙。
  • stackoverflow.com/a/238607/309483: "如果使用 ftell,则必须以二进制模式打开文件。如果以文本模式打开, ftell 仅返回一个“cookie”,该“cookie”仅供 fseek 使用。 "
  • @Michael - 使用 calloc 代替 malloc 意味着您不必输入 source[++newLen] = '\0';
【解决方案2】:

是的 - 你可能会因为你对 strcat 的严重滥用而被捕!

看一下 getline() 它一次读取一行数据,但重要的是它可以限制您读取的字符数,因此您不会溢出缓冲区。

Strcat 相对较慢,因为它必须在每次插入字符时搜索整个字符串的结尾。 您通常会保留一个指向字符串存储当前结尾的指针,并将其传递给 getline 作为读取下一行的位置。

【讨论】:

    【解决方案3】:

    如果你使用的是 linux 系统,一旦你有了文件描述符,你就可以使用 fstat() 获得很多关于文件的信息

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

    所以你可能有

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

    这避免了寻找文件的开头和结尾。

    【讨论】:

      【解决方案4】:

      请参阅this article from JoelOnSoftware,了解您不想使用strcat 的原因。

      查看fread 的替代方案。读取字节或字符时,将其与 1 一起使用。

      【讨论】:

        【解决方案5】:

        你为什么不直接使用你拥有的字符数组呢?应该这样做:

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

        【讨论】:

          【解决方案6】:

          未经测试,但应该可以工作。是的,使用 fread 可以更好地实现,我将把它作为练习留给读者。

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

          【讨论】:

          • realloc 会不会很慢?
          • 排序,但你真的需要担心char *buffer[DEFAULT_SIZE],因为它是一个指针数组,而不是字符数组。 buffer[i] 的分配充其量是可疑的; fgetc() 返回 char,而不是 char *。如果我们假装它是char *buffer = 0;,那么您就快到了。您需要将字符读入int,并且只有在确定它不是 EOF 并且有足够空间时才将其存储在数组中。 while (!feof(file)) is always wrong!这个答案需要大量的工作(但它可能是一个好的答案的基础)。
          【解决方案7】:

          【讨论】:

          • 如果您能详细说明来源的内容,那就太好了。
          【解决方案8】:

          你考虑过 mmap() 吗?您可以直接从文件中读取,就好像它已经在内存中一样。

          http://beej.us/guide/bgipc/output/html/multipage/mmap.html

          【讨论】:

            猜你喜欢
            • 2018-10-02
            • 2021-09-26
            • 1970-01-01
            • 2014-01-06
            • 2018-07-21
            • 2013-09-19
            • 2014-11-21
            • 2011-07-05
            • 1970-01-01
            相关资源
            最近更新 更多