簡體   English   中英

在讀取文件但無限循環的 C 中創建 function

[英]Creating a function in C that reads a file but it infinitely loops

我基本上想打印出我制作的文件中的所有行,但它只是一遍又一遍地循環回到開頭,基本上因為在 function 我設置了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;
}

這是我到目前為止的工作

繼續我的評論,你的想法是正確的,你只是沒有按照正確的順序把各個部分放在一起。 main()中,您正在循環調用 function,分配一行,然后輸出一行文本並一遍又一遍地執行此操作。 (並且沒有釋放您分配的任何 memory - 創建每行讀取的 memory 泄漏)

如果您正在分配存儲來保存該行,您通常希望通過文件分配和存儲所有行,並返回指向您的行集合的指針,以便在main()中打印main() (或任何調用 function 是)。

您可以通過添加一個額外的間接級別並讓您的 function 返回char **來做到這一點。 這是一個簡單的兩步過程,您可以:

  1. 分配一個包含指針的 memory 塊(每行一個指針)。 由於您事先不知道有多少行,因此您只需分配一些指針,然后在realloc()時重新分配更多指針;

  2. 對於您讀取的每一行,您分配length + 1字符的存儲空間並將當前行復制到 memory 的該塊中,將該行的地址分配給您的下一個可用指針。

(您要么跟蹤分配的指針和行數,要么在最后一個指針分配一行后提供一個設置為NULL的附加指針作為哨兵——由您決定,只需使用計數器跟蹤可能在概念上更容易)

閱讀最后一行后,您只需將指針返回到分配給調用者使用的指針集合。 (您也可以將char **的地址作為參數傳遞給您的 function,導致類型為char *** ,但成為三星級程序員並不總是一種恭維)。 但是,這樣做並沒有錯,在某些情況下,它是必需的,但如果你有替代方案,那通常是首選路線。

那么這將如何在實踐中發揮作用呢?

只需將 function 返回類型更改為char **並將附加指針傳遞給計數變量,以便在從 function 返回之前使用讀取的行數更新該地址處的值。 例如,你可以這樣做:

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

這會將您的文件指針指向打開的文件 stream ,然后從中讀取每一行,為該行分配存儲空間並將該分配的地址分配給您的一個指針。 在 function 中,您將使用足夠大的字符數組來保存您使用fgets()讀取的每一行。 從末尾修剪'\n'並獲取長度,然后分配length + 1個字節來保存該行。 將新分配的塊的地址分配給一個指針,然后從您的數組復制到新分配的塊。

strdup()既可以分配也可以復制,但它不是標准庫的一部分,它是 POSIX ——盡管如果您提供正確的選項,大多數編譯器都支持它作為擴展)

readfile()下面 function 把它放在一起,從一個指針開始,當你用完時重新分配兩倍的當前數字(這提供了所需的分配數量和指針數量之間的合理權衡。(僅 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;
}

注意:完成后不要忘記釋放您分配的 memory。當您調用在其他函數中分配的函數時,這變得至關重要。在main()中,memory 將在退出時自動釋放,但要養成良好的習慣.)

示例使用/輸出

$ ./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 行,直到系統的物理限制 memory(如果您的行長於 1023 個字符,請調整MAXC )。

Memory 使用/錯誤檢查

在您編寫的任何動態分配 memory 的代碼中,對於分配的 memory 的任何塊,您有兩個責任:(1)始終保留指向 ZCD69B4957F06CD818D7BF3D21980 塊的起始地址的指針,所以它可以被釋放,更需要。

您必須使用 memory 錯誤檢查程序,以確保您不會嘗試訪問 memory 或寫入超出/超出分配塊的邊界,嘗試讀取或基於未初始化值的條件跳轉,最后確認釋放所有已分配的 memory。

對於 Linux valgrind是正常的選擇。 每個平台都有類似的 memory 檢查器。 它們都易於使用,只需通過它運行您的程序即可。

$ 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)

始終確認您已釋放所有已分配的 memory 並且沒有 memory 錯誤。

該示例使用的完整代碼僅包含標頭,但為了完整起見,將其包含在下面:

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

如果您還有其他問題,請告訴我。

如果你想讀到最后一行,你可以簡單地使用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 逐行讀取文件

您的方法是錯誤的,很可能會產生無限循環。 我將解釋為什么使用原始代碼和內聯注釋:

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是錯誤的。 您的程序將一次又一次地打印同一行,並且您有 memory 泄漏,因為您從不調用free(pInput)

所以你需要重新設計你的代碼。 閱讀fgets做了什么,例如這里https://man7.org/linux/man-pages/man3/fgets.3p.html

需要解決的一些問題:

  • 使用fgets並不能保證您在 function 返回后讀取一行。 因此,如果您真的想檢查您是否閱讀了完整的行,請檢查返回字符串中的字符數,並檢查字符串末尾是否存在換行符。

  • 您對fseek的使用在這里很有趣,因為它的作用是告訴 stream 指向 go 的指針回到文件的開頭,並從那里開始讀取。 這意味着在第一次調用freadline function 后,您將繼續每次從文件中讀取第一個字節。

  • 最后,你的程序像貪婪的嬰兒一樣囤積memory! 您永遠不會釋放您所做的任何分配!

話雖如此,這是一個改進的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;
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM