C逐行读取文件

lro*_*ron 165 c file-io std line

我写了这个函数来从文件中读取一行:

const char *readLine(FILE *file) {

    if (file == NULL) {
        printf("Error: file pointer is null.");
        exit(1);
    }

    int maximumLineLength = 128;
    char *lineBuffer = (char *)malloc(sizeof(char) * maximumLineLength);

    if (lineBuffer == NULL) {
        printf("Error allocating memory for line buffer.");
        exit(1);
    }

    char ch = getc(file);
    int count = 0;

    while ((ch != '\n') && (ch != EOF)) {
        if (count == maximumLineLength) {
            maximumLineLength += 128;
            lineBuffer = realloc(lineBuffer, maximumLineLength);
            if (lineBuffer == NULL) {
                printf("Error reallocating space for line buffer.");
                exit(1);
            }
        }
        lineBuffer[count] = ch;
        count++;

        ch = getc(file);
    }

    lineBuffer[count] = '\0';
    char line[count + 1];
    strncpy(line, lineBuffer, (count + 1));
    free(lineBuffer);
    const char *constLine = line;
    return constLine;
}
Run Code Online (Sandbox Code Playgroud)

该函数正确读取文件,并使用printf我看到constLine字符串也正确读取.

但是,如果我使用这样的功能:

while (!feof(myFile)) {
    const char *line = readLine(myFile);
    printf("%s\n", line);
}
Run Code Online (Sandbox Code Playgroud)

printf输出乱码.为什么?

mba*_*off 282

如果您的任务不是发明逐行读取功能,而只是逐行读取文件,您可以使用涉及该getline()功能的典型代码片段(请参见此处的手册页):

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

int main(void)
{
    FILE * fp;
    char * line = NULL;
    size_t len = 0;
    ssize_t read;

    fp = fopen("/etc/motd", "r");
    if (fp == NULL)
        exit(EXIT_FAILURE);

    while ((read = getline(&line, &len, fp)) != -1) {
        printf("Retrieved line of length %zu:\n", read);
        printf("%s", line);
    }

    fclose(fp);
    if (line)
        free(line);
    exit(EXIT_SUCCESS);
}
Run Code Online (Sandbox Code Playgroud)

  • 这不便携. (75认同)
  • 对于那些说这个getline特定于GNU libc的人来说,"getline()和getdelim()都是最初的GNU扩展.它们在POSIX.1-2008中被标准化了." (42认同)
  • `if(line)`检查是多余的.调用`free(NULL)`本质上是一个无操作. (26认同)
  • 更确切地说,这个'getline`特定于GNU libc,即Linux.但是,如果意图是具有行读取功能(与学习C相反),则Web上有几种公共域行读取功能. (14认同)
  • 我为什么要那样做?阅读手册,在每次调用时重新分配缓冲区,然后在结束时释放它. (11认同)
  • @PhilipAdler如果你真的想要争论`free(NULL)`是非指定的(虽然我很确定它没有这样写),那么你应该知道即使`ls`调用`free(NULL)`.检查后,手册页说"free(ptr); free(ptr);`是未定义的,而`free(NULL)`什么都不做.@mbaitoff然后你为什么要放心释放`line`呢?尽管如此,这个网站都是关于教学或帮助尽可能最好的**解决方案,并且释放不再使用的每个已分配的内存实际上是一种好的做法. (7认同)
  • @ gg.kaspersky我不是在圣书中找到神圣信息的专家.事实是,在现实生活中资源被释放.我认为短代码摘录仅提供概念,不需要包含初始化/取消初始化的所有负担. (5认同)
  • 说实话,这个答案直接来自手册页.如果要讨论有关正确的内存分配/释放的问题,那么它应该作为一个单独的问题提出,而且肯定已经存在.OP显然只问了一个关于手头功能及其目的的问题 - 逐行读取文件.因此,这个问题应该与正确的内存管理或"最佳实践"无关.它与原始问题无关. (5认同)
  • 另外我觉得你想在那个while循环结束之前"自由(换行)" (4认同)
  • @PhilipAdler - "`free(NULL)`是一个未定义的操作......":来自C11标准,§7.22.3.3/ 2,["如果`ptr`是空指针,则不会发生任何操作." ](http://port70.net/~nsz/c/c11/n1570.html#7.22.3.3p2)C89和C99标准说同样的话.所以,`free(NULL)`是一个_defined_非操作. (4认同)
  • @PhilipAdler - 然后谁写了规范做了一个愚蠢/粗心的事情.虽然[手册页](http://cf.ccmr.cornell.edu/cgi-bin/w3mman2html.cgi?free(3))说"如果ptr为NULL,则不执行任何操作".这对我来说是一个很好的定义. (3认同)
  • @PhilipAdler——我评论中的链接是 C11 标准草案。但您也可以阅读[此](/sf/answers/442820381/)和[此](/sf/answers/165232371/)。显然,在 C89 之前,某些实现会因空指针而崩溃。 (2认同)
  • 甚至 C90 **§7.10.3.2 `free` 函数**也表示_如果 `ptr` 是空指针。没有任何操作发生。_ 只有标准之前的 C 库才可以随意错误处理“free(NULL)”。C90 标准发布后,一些库可能几年都没有升级,但到了 90 年代中期,使用 `free(NULL)` 通常是安全的 - 或者,更合理的是,使用 `free(ptr) ` 其中 `ptr` 恰好是一个空指针。 (2认同)

Gil*_*il' 20

在你的readLine函数中,你返回一个指向line数组的指针(严格地说,指向它的第一个字符的指针,但这里的差异是无关紧要的).由于它是一个自动变量(即它在"堆栈中"),因此在函数返回时回收内存.你看到胡言乱语,因为printf它把自己的东西放在堆栈上.

您需要从函数返回动态分配的缓冲区.你已经拥有一个,它是lineBuffer; 你所要做的就是将它截断到所需的长度.

    lineBuffer[count] = '\0';
    realloc(lineBuffer, count + 1);
    return lineBuffer;
}
Run Code Online (Sandbox Code Playgroud)

ADDED(对注释中的后续问题的响应):readLine返回指向组成该行的字符的指针.此指针是您处理行内容所需的.free当你使用完这些字符所占用的内存时,它也是你必须传递的内容.以下是您可以使用该readLine功能的方法:

char *line = readLine(file);
printf("LOG: read a line: %s\n", line);
if (strchr(line, 'a')) { puts("The line contains an a"); }
/* etc. */
free(line);
/* After this point, the memory allocated for the line has been reclaimed.
   You can't use the value of `line` again (though you can assign a new value
   to the `line` variable if you want). */
Run Code Online (Sandbox Code Playgroud)


Rob*_*Rob 20

FILE* fp;
char buffer[255];

fp = fopen("file.txt", "r");

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

fclose(fp);
Run Code Online (Sandbox Code Playgroud)

  • 为什么要强制转换`(FILE *)fp`?fp已经不是一个文件*并且fopen()返回一个文件*吗? (4认同)
  • 如果您同意将行限制在一定长度内,那么这是最好的答案。否则使用“getline”是一个不错的选择。我同意“FILE *”强制转换是不必要的。 (2认同)

Rev*_*Lab 13

//open and get the file handle
FILE* fh;
fopen_s(&fh, filename, "r");

//check if file exists
if (fh == NULL){
    printf("file does not exists %s", filename);
    return 0;
}


//read line by line
const size_t line_size = 300;
char* line = malloc(line_size);
while (fgets(line, line_size, fh) != NULL)  {
    printf(line);
}
free(line);    // dont forget to free heap memory
Run Code Online (Sandbox Code Playgroud)

  • 这段代码有一些问题:`fopen_s` 使代码不可移植。`printf` 将查找格式说明符和_不_打印百分号和以下字符_原样_。空字节将使该行其余部分中的所有字符消失。(不要告诉我空字节不会发生!) (2认同)
  • printf(line) 错了!不要这样做。这将您的代码打开一个字符串格式漏洞,您可以通过正在打印的内容自由地直接读取/写入内存。如果我将 %n/%p 放在文件中并将指针指向我控制的内存中的地址(在文件中的字符串中),我可以执行该代码。 (2认同)

qrd*_*rdl 10

readLine() 返回指向局部变量的指针,这会导致未定义的行为.

您可以:

  1. 在调用函数中创建变量并将其地址传递给 readLine()
  2. 分配内存以供line使用malloc()- 在这种情况下line将是持久的
  3. 使用全局变量,虽然这通常是一种不好的做法


gsa*_*ras 9

一个完整的fgets()解决方案:

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

#define MAX_LEN 256

int main(void)
{
    FILE* fp;
    fp = fopen("file.txt", "r");
    if (fp == NULL) {
      perror("Failed: ");
      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;
}
Run Code Online (Sandbox Code Playgroud)

输出:

First line of file
Second line of file
Third (and also last) line of file
Run Code Online (Sandbox Code Playgroud)

请记住,如果您想从标准输入(而不是本例中的文件)中读取,那么您所要做的就是将其stdin作为fgets()方法的第三个参数传递,如下所示:

while(fgets(buffer, MAX_LEN - 1, stdin))
Run Code Online (Sandbox Code Playgroud)

附录

从 fgets() 输入中删除尾随换行符

c - 如何检测文件是否在c中打开


小智 7

用于fgets()从文件句柄中读取一行.