【问题标题】:Creating a function in C that reads a file but it infinitely loops在 C 中创建一个读取文件但无限循环的函数
【发布时间】:2021-08-08 07:24:15
【问题描述】:

我基本上想打印出我制作的文件中的所有行 但它只是一遍又一遍地循环回到开始 基本上是因为我设置了fseek(fp, 0, SEEK_SET);这部分的功能 但是我不知道我会如何放置它以通过所有其他线路 我基本上每次都回到起点。

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

char *freadline(FILE *fp);

int main(){

    FILE *fp = fopen("story.txt", "r");

    if(fp == NULL){
        printf("Error!");
    }else{
        char *pInput;
        while(!feof(fp)){
            pInput = freadline(fp);
            printf("%s\n", pInput); // outpu 
        }   
    }
    
    return 0;
}

char *freadline(FILE *fp){
    int i;
    for(i = 0; !feof(fp); i++){
        getc(fp);
    }
    fseek(fp, 0, SEEK_SET); // resets to origin (?)
    char *pBuffer = (char*)malloc(sizeof(char)*i);
    pBuffer = fgets(pBuffer, i, fp);

    return pBuffer;
}

这是我目前的工作

【问题讨论】:

  • 你会想看Why is while ( !feof (file) ) always wrong? 你想rewind() 清除EOF。你为什么要循环使用getc()??
  • arpi314 谁或什么文字建议while(!feof(fp)){?
  • @chux-ReinstateMonica 好吧,我希望它遍历 .txt 文件中的所有行,这就是我这样做的原因。不知道怎么做。有什么建议吗?
  • @DavidC.Rankin 我希望它遍历 .txt 文件中的所有行,这就是我这样做的原因。不知道怎么做。有什么建议吗?
  • char buffer[1024]; 然后while (fgets (buffer, sizeof buffer, fp)) { /* do your stuff here */ }(用于面向行的读取)。如果要存储所有行,则需要返回char **(分配指针,用完时realloc()),然后为每一行分配,复制到新分配并依次为每个指针分配起始地址)许多,很多例子已经在 SO 上。

标签: c


【解决方案1】:

如果你想读到最后一行的罚款,你可以简单地使用getc()。当到达文件末尾或读取失败时,它返回 EOF。因此,如果使用getc(),为了确保到达文件末尾,最好使用feof(),如果到达文件末尾则返回非零值,否则返回0。

示例:-

int main()
{
  FILE *fp = fopen("story.txt", "r");
 int ch = getc(fp);
  while (ch != EOF) 
  {
    /* display contents of file on screen */ 
    putchar(ch); 

    ch = getc(fp);
   }

    if (feof(fp))
       printf("\n End of file reached.");
    else 
       printf("\nError while reading!");
    fclose(fp);
  
    getchar();
    return 0;
  }

你也可以fgets(),下面是例子:-

#define MAX_LEN 256

int main(void)
{
    FILE *fp = fopen("story.txt", "r");
    if (fp == NULL) {
      perror("Failed: "); //prints a descriptive error message to stderr.
      return 1;
     }

    char buffer[MAX_LEN];
    // -1 to allow room for NULL terminator for really long string
    while (fgets(buffer, MAX_LEN - 1, fp))
    {
        // Remove trailing newline
        buffer[strcspn(buffer, "\n")] = 0;
        printf("%s\n", buffer);
     }

    fclose(fp);
    return 0;
}

或者,

int main() {

int MAX_LEN = 255;
char buffer[MAX_LEN];

FILE *fp = fopen("story.txt", "r");

while(fgets(buffer, MAX_LEN, fp)) {
    printf("%s\n", buffer);
}

fclose(fp);
return 0;
}

更多详情请参考C read file line by line

【讨论】:

  • 嗨!这种方法很棒,我实际上尝试了它的变体!只是我们的老师严格说我们应该使用char * freadline(FILE *fp)作为我们使用fgets的函数:(
  • 另外,他要求我们分配与应该的大小相同的内存
  • 我希望您意识到使用fgets 并不能保证您在返回后读取一行。如果一行比MAX_LEN 长,您使用 fgets 的方法将在到达行尾之前打印多个换行符。
【解决方案2】:

您的方法是错误的,很可能会产生无限循环。我将解释为什么使用原始代码和内联 cmets:

char *freadline(FILE *fp){
    int i;

    // This part attempts to count the number of characters
    // in the whole file by reading char-by-char until EOF is set
    for(i = 0; !feof(fp); i++){
        getc(fp);
    }

    // Here EOF is set

    // This returns to the start and clears EOF
    fseek(fp, 0, SEEK_SET);

    // Here EOF is cleared

    char *pBuffer = (char*)malloc(sizeof(char)*i);

    // Here you read a line, i.e. you read characters until (and including) the
    // first newline in the file.
    pBuffer = fgets(pBuffer, i, fp);

    // Here EOF is still cleared as you only read the first line of the file
    
    return pBuffer;
}

所以在main 这样做时

while(!feof(fp)){
    ...
}

你有一个无限循环,因为feof 是假的。您的程序将一次又一次地打印同一行,并且您有内存泄漏,因为您从未调用过free(pInput)。

所以你需要重新设计你的代码。阅读 fgets 做什么,例如这里https://man7.org/linux/man-pages/man3/fgets.3p.html

【讨论】:

  • 是的,是的,我完全理解,但我不知道在不使用 fseek 或 rewind 的情况下将文件重置到它应该在的位置
  • @arpi314 好吧,问题出在第一部分。你不需要它。无需计算整个文件中的所有字符。仅使用fgets
【解决方案3】:

从我的 cmets 开始,您的思路是正确的,只是您没有按照正确的顺序将各个部分放在一起。在main() 中,您正在循环调用您的函数,分配一行,然后输出一行文本并一遍又一遍地执行此操作。 (并且没有释放你分配的任何内存——每行读取都会造成内存泄漏)

如果您正在分配存储来保存行,您通常希望通过文件分配和存储所有行并返回指向您的行集合的指针在main()(或任何调用函数)中打印。

您可以通过添加一个额外的间接级别并让您的函数返回char ** 来做到这一点。这是一个简单的两步过程,您可以:

  1. 分配一块包含指针的内存(每行一个指针)。因为你不会事先知道多少行,你只需分配一些指针,然后当你用完时realloc()更多的指针;

  2. 对于您读取的每一行,您分配length + 1 个字符的存储空间并将当前行复制到该内存块,并将该行的地址分配给您的下一个可用指针。

(您可以跟踪分配的指针和行数,或者在最后一个指针分配行之后提供一个设置为NULL 的附加指针作为哨兵​​m> - 由您决定,简单地用计数器跟踪可能在概念上更容易)

读完最后一行后,您只需将指针返回到分配给调用者使用的指针集合。 (您也可以将char ** 的地址作为参数传递给您的函数,从而导致类型为char ***,但成为三星级程序员 并不总是一种恭维)。但是,这样做并没有错,在某些情况下,这是必需的,但如果您有其他选择,那通常是首选路线。

那么这在实践中如何运作?

只需将您的函数返回类型更改为char ** 并将一个附加指针传递给一个计数变量,这样您就可以使用从函数返回之前读取的行数更新该地址的值。例如,你可以这样做:

char **readfile (FILE *fp, size_t *n);

这会将您的文件指针指向打开的文件流,然后从中读取每一行,为该行分配存储空间并将该分配的地址分配给您的一个指针。在该函数中,您将使用一个足够大的字符数组来保存您使用fgets() 读取的每一行。从末尾修剪'\n' 并获取长度,然后分配length + 1 字节来保存该行。将新分配的块的地址分配给一个指针,然后从数组复制到新分配的块。

(strdup() 既可以分配也可以复制,但它不是标准库的一部分,它是 POSIX ——尽管如果您提供适当的选项,大多数编译器都支持它作为扩展)

在readfile() 函数下方将其全部放在一起,从单个指针开始,并在用完时重新分配两倍于当前数字的内存(这在所需的分配数量和指针数量之间提供了合理的权衡。(在只需 20 次调用 realloc(),您将有 1M 指针)。您可以选择任何您喜欢的重新分配和增长方案,但您要避免为每一行调用 realloc() -- realloc() 仍然是一个相对昂贵的调用。

#define MAXC 1024       /* if you need a constant, #define one (or more) */
#define NPTR    1       /* initial no. of pointers to allocate */

/* readfile reads all lines from fp, updating the value at the address
 * provided by 'n'. On success returns pointer to allocated block of pointers
 * with each of *n pointers holding the address of an allocated block of
 * memory containing a line from the file. On allocation failure, the number
 * of lines successfully read prior to failure is returned. Caller is
 * responsible for freeing all memory when done with it.
 */
char **readfile (FILE *fp, size_t *n)
{
    char buffer[MAXC], **lines;                 /* buffer to hold each line, pointer */
    size_t allocated = NPTR, used = 0;          /* allocated and used pointers */
    
    lines = malloc (allocated * sizeof *lines); /* allocate initial pointer(s) */
    
    if (lines == NULL) {                        /* validate EVERY allocation */
        perror ("malloc-lines");
        return NULL;
    }
    
    while (fgets (buffer, MAXC, fp)) {          /* read each line from file */
        size_t len;                             /* variable to hold line-length */
        if (used == allocated) {                /* is pointer reallocation needed */
            /* always realloc to a temporary pointer to avoid memory leak if
             * realloc fails returning NULL.
             */
            void *tmp = realloc (lines, 2 * allocated * sizeof *lines);
            if (!tmp) {                         /* validate EVERY reallocation */
                perror ("realloc-lines");
                break;                          /* lines before failure still good */
            }
            lines = tmp;                        /* assign reallocted block to lines */
            allocated *= 2;                     /* update no. of allocated pointers */
        }
        buffer[(len = strcspn (buffer, "\n"))] = 0;     /* trim \n, save length */
        
        lines[used] = malloc (len + 1);         /* allocate storage for line */
        if (!lines[used]) {                     /* validate EVERY allocation */
            perror ("malloc-lines[used]");
            break;
        }
        
        memcpy (lines[used], buffer, len + 1);  /* copy buffer to lines[used] */
        used++;                                 /* increment used no. of pointers */
    }
    *n = used;              /* update value at address provided by n */
    
    /* can do final realloc() here to resize exactly to used no. of pointers */
    
    return lines;           /* return pointer to allocated block of pointers */
}

在main() 中,您只需传递您的文件指针和size_t 变量的地址,并在迭代指针之前检查返回值,以使用您需要的任何行(它们简单地打印在下面),例如

int main (int argc, char **argv) {
    
    char **lines;       /* pointer to allocated block of pointers and lines */
    size_t n;           /* number of lines read */
    /* use filename provided as 1st argument (stdin by default) */
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

    if (!fp) {  /* validate file open for reading */
        perror ("file open failed");
        return 1;
    }
    
    lines = readfile (fp, &n);
    
    if (fp != stdin)    /* close file if not stdin */
        fclose (fp);

    if (!lines) {       /* validate readfile() return */
        fputs ("error: no lines read from file.\n", stderr);
        return 1;
    }
    
    for (size_t i = 0; i < n; i++) {            /* loop outputting all lines read */
        puts (lines[i]);
        free (lines[i]);                        /* don't forget to free lines */
    }
    free (lines);                               /* and free pointers */
    
    return 0;
}

(注意:完成后不要忘记释放分配的内存。当您调用在其他函数中分配的函数时,这变得至关重要。在main() 中,内存将退出时自动释放,但要养成良好的习惯。)

使用/输出示例

$ ./bin/readfile_allocate dat/captnjack.txt
This is a tale
Of Captain Jack Sparrow
A Pirate So Brave
On the Seven Seas.

程序将读取任何文件,无论它有 4 行还是 400,000 行,直到系统内存的物理限制(如果您的行长度超过 1023 个字符,请调整 MAXC)。

内存使用/错误检查

在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您都有 2 个职责:(1)始终保留指向起始地址的指针内存块,因此,(2) 当不再需要它时可以释放。

您必须使用内存错误检查程序来确保您不会尝试访问内存或写入超出/超出分配块的边界,尝试读取或基于未初始化的值进行条件跳转,最后, 以确认您已释放所有已分配的内存。

对于 Linux,valgrind 是正常的选择。每个平台都有类似的内存检查器。它们都易于使用,只需通过它运行您的程序即可。

$ valgrind ./bin/readfile_allocate dat/captnjack.txt
==4801== Memcheck, a memory error detector
==4801== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==4801== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==4801== Command: ./bin/readfile_allocate dat/captnjack.txt
==4801==
This is a tale
Of Captain Jack Sparrow
A Pirate So Brave
On the Seven Seas.
==4801==
==4801== HEAP SUMMARY:
==4801==     in use at exit: 0 bytes in 0 blocks
==4801==   total heap usage: 10 allocs, 10 frees, 5,804 bytes allocated
==4801==
==4801== All heap blocks were freed -- no leaks are possible
==4801==
==4801== For counts of detected and suppressed errors, rerun with: -v
==4801== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

始终确认您已释放已分配的所有内存并且没有内存错误。

示例中使用的完整代码仅包含标题,但出于完整性考虑,将其包含在下面:

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

#define MAXC 1024       /* if you need a constant, #define one (or more) */
#define NPTR    1       /* initial no. of pointers to allocate */

/* readfile reads all lines from fp, updating the value at the address
 * provided by 'n'. On success returns pointer to allocated block of pointers
 * with each of *n pointers holding the address of an allocated block of
 * memory containing a line from the file. On allocation failure, the number
 * of lines successfully read prior to failure is returned. Caller is
 * responsible for freeing all memory when done with it.
 */
char **readfile (FILE *fp, size_t *n)
{
    char buffer[MAXC], **lines;                 /* buffer to hold each line, pointer */
    size_t allocated = NPTR, used = 0;          /* allocated and used pointers */
    
    lines = malloc (allocated * sizeof *lines); /* allocate initial pointer(s) */
    
    if (lines == NULL) {                        /* validate EVERY allocation */
        perror ("malloc-lines");
        return NULL;
    }
    
    while (fgets (buffer, MAXC, fp)) {          /* read each line from file */
        size_t len;                             /* variable to hold line-length */
        if (used == allocated) {                /* is pointer reallocation needed */
            /* always realloc to a temporary pointer to avoid memory leak if
             * realloc fails returning NULL.
             */
            void *tmp = realloc (lines, 2 * allocated * sizeof *lines);
            if (!tmp) {                         /* validate EVERY reallocation */
                perror ("realloc-lines");
                break;                          /* lines before failure still good */
            }
            lines = tmp;                        /* assign reallocted block to lines */
            allocated *= 2;                     /* update no. of allocated pointers */
        }
        buffer[(len = strcspn (buffer, "\n"))] = 0;     /* trim \n, save length */
        
        lines[used] = malloc (len + 1);         /* allocate storage for line */
        if (!lines[used]) {                     /* validate EVERY allocation */
            perror ("malloc-lines[used]");
            break;
        }
        
        memcpy (lines[used], buffer, len + 1);  /* copy buffer to lines[used] */
        used++;                                 /* increment used no. of pointers */
    }
    *n = used;              /* update value at address provided by n */
    
    /* can do final realloc() here to resize exactly to used no. of pointers */
    
    return lines;           /* return pointer to allocated block of pointers */
}

int main (int argc, char **argv) {
    
    char **lines;       /* pointer to allocated block of pointers and lines */
    size_t n;           /* number of lines read */
    /* use filename provided as 1st argument (stdin by default) */
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

    if (!fp) {  /* validate file open for reading */
        perror ("file open failed");
        return 1;
    }
    
    lines = readfile (fp, &n);
    
    if (fp != stdin)    /* close file if not stdin */
        fclose (fp);

    if (!lines) {       /* validate readfile() return */
        fputs ("error: no lines read from file.\n", stderr);
        return 1;
    }
    
    for (size_t i = 0; i < n; i++) {            /* loop outputting all lines read */
        puts (lines[i]);
        free (lines[i]);                        /* don't forget to free lines */
    }
    free (lines);                               /* and free pointers */
    
    return 0;
}

如果您还有其他问题,请告诉我。

【讨论】:

  • 嗨!我对这种详细的方法感到非常高兴,并且我喜欢关于它的所有见解。我现在正在处理!有很多事情要处理 oof 我仍然是 C 的菜鸟,但我会尽力了解你所说的一切。但是我们的老师明确说要使用char *freadline(FILE *fp) 的函数。我知道这很可悲,但是是的。我是
【解决方案4】:

需要解决的一些问题:

  • 使用fgets 并不能保证您在函数返回后读取了一行。所以如果你真的想检查你是否阅读了完整的一行,检查返回字符串中的字符数,并检查字符串末尾是否存在换行符。

  • 您对fseek 的使用在这里很有趣,因为它的作用是告诉流指针回到文件的开头,并从那里开始读取。这意味着在第一次调用freadline 函数后,您每次都会继续从文件中读取第一个字节。

  • 最后,您的程序正在像贪婪的婴儿一样囤积内存!您永远不会释放您所做的任何分配!

话虽如此,这里有一个改进的freadline 实现:

char *freadline(FILE *fp) {
    /* initializations */
    char    buf[BUFSIZ + 1];
    char   *pBuffer;
    size_t  size = 0, tmp_size;

    /* fgets returns NULL when it reaches EOF, so our loop is conditional
     * on that
     */
    while (fgets (buf, BUFSIZ + 1, fp) != NULL) {
        tmp_size = strlen (buf);
        size += tmp_size;
        if (tmp_size != BUFSIZ || buf[BUFSIZ] == '\n')
            break;
    }

    /* after breaking from loop, check that size is not zero.
     * this should only happen if we reach EOF, so return NULL
     */
    if (size == 0)
        return NULL;

    /* Allocate memory for the line plus one extra for the null byte */
    pBuffer = malloc (size + 1);

    /* reads the contents of the file into pBuffer */
    if (size <= BUFSIZ) {
        /* Optimization: use memcpy rather than reading 
         * from disk if the line is small enough
         */
        memcpy (pBuffer, buf, size);
    } else {
        fseek (fp, ftell(fp) - size, SEEK_SET);
        fread (pBuffer, 1, size, fp);
    }
    pBuffer[size] = '\0'; /* set the null terminator byte */
    return pBuffer; /* remember to free () this when you are done! */
}

这种方式不需要额外调用feof(这通常是命中注定的),而是依靠fgets 返回的内容来确定我们是否已到达文件末尾。

通过这些更改,将main 更改为:

int main() {

    FILE *fp = fopen("story.txt", "r");

    if(fp == NULL){
        printf("Error!");
    }else{
        char *pInput;
        /* here we just keep reading until a NULL string is returned */
        for (pInput = freadline(fp); pInput != NULL; pInput = freadline(fp))) {
            printf("%s", pInput); // output
            free (pInput);
        }
        fclose(fp);
    }
    return 0;
}

【讨论】:

  • 你好,请问BUFSIZ是做什么的?
  • 能否也通过每一行询问详细的做法?说到 C,我有点菜鸟。
  • 我还尝试使用一个包含 3 行的文件运行您的代码,但它没有打印出另外两行。我很困惑。
  • @arpi314 是的,这就是我在睡觉前编写代码而不是测试它所得到的。我认为问题在于我可能没有用\0 终止返回的字符串。我还添加了 cmets。至于BUFSIZ 是什么,我不会太在意这个。我只是使用它,因为它是一个存在于stdio.h 中的宏,并且它不为零。它还有其他用途 WRT 缓冲流,但这是另一个主题。
猜你喜欢
  • 2012-03-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-24
  • 1970-01-01
相关资源
最近更新 更多