C逐行读取文件


183

我编写了此函数来从文件中读取一行:

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

该函数正确读取文件,并且使用printf,我也发现constLine字符串也得到了正确读取。

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

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

printf输出乱码。为什么?


使用fgets代替fgetc。您正在逐字符而不是逐行阅读。
希夫

3
请注意,这getline()是POSIX 2008的一部分。可能有类似POSIX的平台,没有它,尤其是如果它们不支持POSIX 2008的其余部分,但是在POSIX系统的世界范围内,getline()这些天已经很轻便了。
乔纳森·莱夫勒

Answers:


304

如果您的任务不是发明逐行读取功能,而是仅逐行读取文件,则可以使用涉及该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);
}

82
那不是便携式的。
JeremyP

16
更确切地说,这getline特定于GNU libc,即Linux。但是,如果打算具有行读取功能(而不是学习C语言),则Web上有几种公共领域的行读取功能。
吉尔(Gilles)“所以,别再邪恶了”,2010年

11
我为什么要那么做?阅读手册,在每次调用时重新分配缓冲区,然后应在最后释放它。
mbaitoff 2012年

29
if(line)检查是多余的。呼叫free(NULL)本质上是无操作的。
aroth

49
对于那些说此getline特定于GNU libc的人来说,“ getline()和getdelim()最初都是GNU扩展。它们在POSIX.1-2008中进行了标准化。”
willkill07年

37
FILE* filePointer;
int bufferLength = 255;
char buffer[bufferLength];

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

while(fgets(buffer, bufferLength, filePointer)) {
    printf("%s\n", buffer);
}

fclose(filePointer);

对我来说,这导致用下一行覆盖每一行。根据以上答案查看此问题
Cezar Cobuz

5
为什么投(FILE*) fp?是不是fp已经FILE *fopen()返回了FILE *
会计师,

1
如果行的行数被限制为一定长度,那么这是最好的答案。否则,使用getline是一个很好的选择。我同意FILE *演员表是不必要的。
theicfire

我删除了不必要的强制类型转换,为缓冲区长度添加了一个变量,并更改fpfilePointer以更清楚。
罗布

21

readLine函数中,您将返回一个指向line数组的指针(严格来说,是指向其第一个字符的指针,但是此处的区别无关紧要)。由于它是一个自动变量(即“在栈上”),因此在函数返回时将回收内存。您会看到乱码,因为printf已将其自身的内容放入堆栈中。

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

    lineBuffer[count] = '\0';
    realloc(lineBuffer, count + 1);
    return lineBuffer;
}

添加(回应评论中的后续问题):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). */

@Iron:我已经在回答中添加了一些内容,但是我不确定您的困难所在,因此可能不合时宜。
吉尔(Gilles)“所以,别再邪恶了”,2010年

@Iron:答案是您不释放它。您在API文档中记录了以下事实:调用者必须释放返回的缓冲区是malloc和sd的事实。然后使用您的readLine函数的人(希望!)将编写类似于Gilles添加到他的答案中的代码段的代码。
JeremyP

14
//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

1
此代码存在一些问题:fopen_s使代码不可移植。printf将查找格式说明符,而不按原样打印百分号和以下字符。空字节将使该行其余部分中的所有字符消失。(不要告诉我,不能发生空字节!)
hagello

而且,您不能解决问题。OP描述其功能的返回值消失了。我看不到您正在解决此问题。
哈杰罗

@Hartley我知道这是一个较旧的注释,但是我添加此注释是为了使某人不会阅读他的注释并尝试在循环中释放(行)。行的内存仅在循环开始前分配一次,因此在循环结束后应仅释放一次。如果尝试释放循环中的行,则会得到意外的结果。取决于free()处理指针的方式。如果它只是重​​新分配内存而使指针指向旧位置,则代码可能会起作用。如果它为指针分配了另一个值,那么您将覆盖内存的另一个部分。
alaniane

2
printf(line)是错误的!不要这样做。这将使您的代码暴露于字符串格式漏洞,在这里您可以通过打印的东西自由地直接将其直接读/写到内存中。如果我将%n /%p放在文件中,然后将指针指向我所控制的内存中地址(在文件中的字符串中),则可以执行该代码。
oxagast

10

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

到处走走,您可以:

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


4

该示例有些错误:

  • 您忘记在\ printfs中添加\ n。错误消息也应该去stderr即fprintf(stderr, ....
  • (不是biggy,而是)考虑使用fgetc()而不是getc()getc()是宏,fgetc()是适当的功能
  • getc()返回一个intso,ch应将其声明为int。这很重要,因为与的比较EOF将正确处理。某些8位字符集0xFF用作有效字符(ISO-LATIN-1将作为示例),如果分配给EOF,则为-1 。0xFFchar
  • 生产线上可能存在缓冲区溢出

    lineBuffer[count] = '\0';

    如果该行的长度恰好是128个字符,count则在执行该点时为128。

  • 正如其他人指出的那样,line是一个本地声明的数组。您不能返回指向它的指针。

  • strncpy(count + 1)最多可复制count + 1字符,但如果命中则终止,'\0' 因为您设置lineBuffer[count]'\0'您知道它将永远不会到达count + 1。但是,如果这样做了,它就不会终止'\0',因此您需要这样做。您经常会看到类似以下的内容:

    char buffer [BUFFER_SIZE];
    strncpy(buffer, sourceString, BUFFER_SIZE - 1);
    buffer[BUFFER_SIZE - 1] = '\0';
  • 如果malloc()要返回的行(代替本地char数组),则返回类型应为char*- const


2
void readLine(FILE* file, char* line, int limit)
{
    int i;
    int read;

    read = fread(line, sizeof(char), limit, file);
    line[read] = '\0';

    for(i = 0; i <= read;i++)
    {
        if('\0' == line[i] || '\n' == line[i] || '\r' == line[i])
        {
            line[i] = '\0';
            break;
        }
    }

    if(i != read)
    {
        fseek(file, i - read + 1, SEEK_CUR);
    }
}

这个如何?


2

这是我的几个小时...逐行读取整个文件。

char * readline(FILE *fp, char *buffer)
{
    int ch;
    int i = 0;
    size_t buff_len = 0;

    buffer = malloc(buff_len + 1);
    if (!buffer) return NULL;  // Out of memory

    while ((ch = fgetc(fp)) != '\n' && ch != EOF)
    {
        buff_len++;
        void *tmp = realloc(buffer, buff_len + 1);
        if (tmp == NULL)
        {
            free(buffer);
            return NULL; // Out of memory
        }
        buffer = tmp;

        buffer[i] = (char) ch;
        i++;
    }
    buffer[i] = '\0';

    // Detect end
    if (ch == EOF && (i == 0 || ferror(fp)))
    {
        free(buffer);
        return NULL;
    }
    return buffer;
}

void lineByline(FILE * file){
char *s;
while ((s = readline(file, 0)) != NULL)
{
    puts(s);
    free(s);
    printf("\n");
}
}

int main()
{
    char *fileName = "input-1.txt";
    FILE* file = fopen(fileName, "r");
    lineByline(file);
    return 0;
}

1
为什么用fgetc代替fgets
theicfire

1
const char *readLine(FILE *file, char* line) {

    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);
    return line;

}


char linebuffer[256];
while (!feof(myFile)) {
    const char *line = readLine(myFile, linebuffer);
    printf("%s\n", line);
}

请注意,“ line”变量在调用函数中声明,然后传递,因此您的readLine函数将填充预定义的缓冲区并仅返回它。这是大多数C库工作的方式。

我知道还有其他方法:

  • 将其定义char line[]为静态(static char line[MAX_LINE_LENGTH] ->从函数返回后将保留其值)。->错误,该函数不可重入,并且会发生竞争状态->如果您从两个线程调用两次,它将覆盖其结果
  • malloc()启用char line [],并在调用函数中将其释放->太多昂贵的mallocs,并委派将缓冲区释放给另一个函数的责任(最优雅的解决方案是调用malloc和调用free同一函数中的任何缓冲区)

顺便说一句,从“强制”强制char*转换const char*是多余的。

btw2,不需要malloc()lineBuffer,只需定义它char lineBuffer[128],所以您不需要释放它

btw3不要使用“动态大小的堆栈数组”(将数组定义为char arrayName[some_nonconstant_variable]),如果您不完全知道自己在做什么,则只能在C99中使用。


1
请注意,“ line”变量是在调用函数中声明的,然后传递给您-您可能应该删除函数中本地的line声明。另外,你需要告诉函数缓冲区有多长,你是路过,认为战略的处理太长线缓冲区您传递英寸
JeremyP

1

您应该使用ANSI函数读取一行,例如。fgets。调用后,需要在调用上下文中使用free(),例如:

...
const char *entirecontent=readLine(myFile);
puts(entirecontent);
free(entirecontent);
...

const char *readLine(FILE *file)
{
  char *lineBuffer=calloc(1,1), line[128];

  if ( !file || !lineBuffer )
  {
    fprintf(stderr,"an ErrorNo 1: ...");
    exit(1);
  }

  for(; fgets(line,sizeof line,file) ; strcat(lineBuffer,line) )
  {
    if( strchr(line,'\n') ) *strchr(line,'\n')=0;
    lineBuffer=realloc(lineBuffer,strlen(lineBuffer)+strlen(line)+1);
    if( !lineBuffer )
    {
      fprintf(stderr,"an ErrorNo 2: ...");
      exit(2);
    }
  }
  return lineBuffer;
}

1

实现从文件(input1.txt)读取和获取内容的方法

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

void testGetFile() {
    // open file
    FILE *fp = fopen("input1.txt", "r");
    size_t len = 255;
    // need malloc memory for line, if not, segmentation fault error will occurred.
    char *line = malloc(sizeof(char) * len);
    // check if file exist (and you can open it) or not
    if (fp == NULL) {
        printf("can open file input1.txt!");
        return;
    }
    while(fgets(line, len, fp) != NULL) {
        printf("%s\n", line);
    }
    free(line);
}

希望能有所帮助。编码愉快!


0

您错误地返回了指向自动变量的指针。变量行在堆栈中分配,并且仅在函数有效期内存在。您不可以返回指向它的指针,因为一旦它返回,内存将在其他地方给出。

const char* func x(){
    char line[100];
    return (const char*) line; //illegal
}

为了避免这种情况,您可以返回一个指向位于例如堆上的内存的指针。lineBuffer,并在完成后调用free()是用户的责任。或者,您可以要求用户将一个存储地址作为参数传递给您,在该地址上写入行内容。


非法行为和未定义行为^^之间有区别。
2013年

0

我想要一个从0开始的代码,所以我这样做是逐行读取字典中单词的内容。

char temp_str [20]; //您可以根据需要更改缓冲区大小,也可以根据文件中一行的长度进行更改。

注意每次读取行时我都会使用Null字符初始化缓冲区。此功能可以自动执行,但是由于我需要概念验证并且想要逐字节设计程序

#include<stdio.h>

int main()
{
int i;
char temp_ch;
FILE *fp=fopen("data.txt","r");
while(temp_ch!=EOF)
{
 i=0;
  char temp_str[20]={'\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0'};
while(temp_ch!='\n')
{
  temp_ch=fgetc(fp);
  temp_str[i]=temp_ch;
  i++;
}
if(temp_ch=='\n')
{
temp_ch=fgetc(fp);
temp_str[i]=temp_ch;
}
printf("%s",temp_str);
}
return 0;
}

如果方括号放在正确的位置,则您的程序将工作;)例如int main() {
dylnmc

顺便说一句,您无需指定所有20个'\ 0'。您可以这样写: codechar temp_str [20] = {'\ 0'}; code c会自动使用空终止符填充每个插槽,因为数组声明的工作方式是:如果使用较少的元素初始化数组,则最后一个元素将填充剩余的元素。
alaniane

我相信char temp_str[20] = {0}还会用空终止符填充整个字符数组。
周恩屯

0

我的工具从零开始:

FILE *pFile = fopen(your_file_path, "r");
int nbytes = 1024;
char *line = (char *) malloc(nbytes);
char *buf = (char *) malloc(nbytes);

size_t bytes_read;
int linesize = 0;
while (fgets(buf, nbytes, pFile) != NULL) {
    bytes_read = strlen(buf);
    // if line length larger than size of line buffer
    if (linesize + bytes_read > nbytes) {
        char *tmp = line;
        nbytes += nbytes / 2;
        line = (char *) malloc(nbytes);
        memcpy(line, tmp, linesize);
        free(tmp);
    }
    memcpy(line + linesize, buf, bytes_read);
    linesize += bytes_read;

    if (feof(pFile) || buf[bytes_read-1] == '\n') {
        handle_line(line);
        linesize = 0;
        memset(line, '\0', nbytes);
    }
}

free(buf);
free(line);

为什么使用堆(malloc)而不是堆栈?似乎有一个更简单的基于堆栈的解决方案fgets可以使用。
theicfire

0

提供可移植的通用getdelim函数,通过msvc,clang,gcc通过测试。

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

ssize_t
portabl_getdelim(char ** restrict linep,
                 size_t * restrict linecapp,
                 int delimiter,
                 FILE * restrict stream) {
    if (0 == *linep) {
        *linecapp = 8;
        *linep = malloc(*linecapp);
        if (0 == *linep) {
            return EOF;
        }
    }

    ssize_t linelen = 0;
    int c = 0;
    char *p = *linep;

    while (EOF != (c = fgetc(stream))) {
        if (linelen == (ssize_t) *linecapp - 1) {
            *linecapp <<= 1;
            char *p1 = realloc(*linep, *linecapp);
            if (0 == *p1) {
                return EOF;
            }
            p = p1 + linelen;
        }
        *p++ = c;
        linelen++;

        if (delimiter == c) {
            *p = 0;
            return linelen;
        }
    }
    return EOF == c ? EOF : linelen;
}


int
main(int argc, char **argv) {
    const char *filename = "/a/b/c.c";
    FILE *file = fopen(filename, "r");
    if (!file) {
        perror(filename);
        return 1;
    }

    char *line = 0;
    size_t linecap = 0;
    ssize_t linelen;

    while (0 < (linelen = portabl_getdelim(&line, &linecap, '\n', file))) {
        fwrite(line, linelen, 1, stdout);
    }
    if (line) {
        free(line);
    }
    fclose(file);   

    return 0;
}

为什么这样fgets存在?
theicfire

fgets是否可以自定义行定界符或自定义当前行的处理方式?
南山竹

getdelim允许自定义定界符。另外我确实注意到没有行长限制-在这种情况下,您可以将堆栈与一起使用getline。(两者均在此处描述:man7.org/linux/man-pages/man3/getline.3.html
theicfire

您仅谈论Linux,问题是关于如何阅读C语言中的代码,对吗?
南山竹

这适用于任何标准c实现(getdelim并且getline在POSIX.1-2008中进行了标准化,此页面上其他人也提到过)。fgets也是标准c语言,而不是特定
theicfire
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.