在C语言中正确地将文本文件读入缓冲区的方法是什么?

29

我正在处理一些小文本文件,想要在处理它们的同时将它们读入缓冲区,因此我想出了以下代码:

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

1
这是错误的。strcat连接字符串。即使&symbol是一个char *,它也没有以null结尾。你应该使用fgetsfread。此外,在您的情况下,strcat无论如何都会很慢,因为每次需要附加一个字符时,它都会扫描source - Alok Singhal
更不用说每次读取一个字符比使用 fread 要慢得多了。 - Nick Meyer
@Nick: 我不确定“慢得多”的情况,因为由于I/O缓存和函数调用的可能内联,性能影响未必那么大;但使用fread()仍然是个好主意。 - Christoph
请查看 mmap() 来进行文件内存映射。注意缓冲区溢出。不要使用 strcat() - 即使您修复了空终止符的问题,它也会给您带来二次方行为,在百万字节文件中很糟糕,在千兆字节文件中更是灾难性的。 - Jonathan Leffler
@Mark:如果 sizeof(int) == 1 怎么办?正如你所说,最好不要依赖它。 - Alok Singhal
显示剩余2条评论
8个回答

88
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()函数将一个以 null 结尾的字符串 s2 复制到另一个以 null 结尾的字符串 s1 的末尾,然后添加一个终止符 `\0'。

你正在将一个字符(而不是以 null 结尾的字符串!)附加到一个可能没有以 null 结尾的字符串上。我唯一能想象这按照手册描述的工作方式的情况是,如果文件中的每个字符都已经以 null 结尾,那么这就毫无意义了。因此,这绝对是对 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! */

2
你可能也想要给你的缓冲区加上空字符。在你的第二个代码示例中,你留出了空字符的位置,但实际上没有设置它;在你的第一个示例中,你忽略了为空字符留出位置。 - Brian Campbell
1
使用 ftell 和 malloc,这是正确的方法。+1 - cigarman
1
如果您使用ftell,则必须以二进制模式打开文件。如果以文本模式打开它,则ftell仅返回一个“cookie”,只能由fseek使用。 - Janus Troelsen
1
@Michael - 使用 calloc 而不是 malloc 意味着您不必放置 source[++newLen] = '\0'; - Joseph
1
我建议在sizeof后面加一个空格,因为它是一个运算符而不是函数调用:sizeof (char) - JohnMudd
显示剩余9条评论

6

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

看一下getline(),它按行读取数据,但重要的是它可以限制你读取的字符数,这样就不会溢出缓冲区。

由于每次插入字符时都必须搜索整个字符串以找到末尾,所以strcat相对较慢。通常情况下,您会保留一个指向字符串存储当前末尾的指针,并将其作为位置传递给getline,以便读取下一行。


6

如果您正在使用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
}

这样可以避免在文件的开头和结尾进行查找。

1

为什么不直接使用你已经有的字符数组呢?这应该可以解决问题:

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

1

没有经过测试,但应该可以正常工作。是的,它可以使用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” 会不会很慢? - ajay
有点像,但你真的需要关注char *buffer[DEFAULT_SIZE],因为它是一个指针数组,而不是字符数组。对于buffer[i]的赋值最好是可疑的;fgetc()返回的是一个char,而不是char *。如果我们假装它是char *buffer = 0;,那么你就快成功了。你需要将字符读入一个int中,并且只有在确定它不是EOF并且有足够的空间时才将其存储在数组中。while (!feof(file))总是错误的!这个答案需要进行相当大的修改(但是可以作为一个好答案的基础)。 - Jonathan Leffler

1

参见JoelOnSoftware的这篇文章,了解为什么不应该使用strcat

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


0

如果您能对源代码的内容进行一些阐述,那将会非常棒。 - adrian

-2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接