C 파일을 한 줄씩 읽습니다.


184

파일에서 한 줄을 읽으려면이 함수를 작성했습니다.

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. 줄 단위 대신 문자 단위로 읽고 있습니다.
Shiv

3
참고 getline()POSIX 2008의 일부가 그들이 POSIX 2008의 나머지 부분을 지원하지 않는 특히, 그것없이 플랫폼 POSIX-처럼,하지만 POSIX 시스템의 세계 내에서 수 getline()요즘 꽤 휴대용입니다.
Jonathan Leffler

답변:


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

83
휴대용이 아닙니다.
JeremyP

16
보다 정확하게는 이것은 getlineGNU libc, 즉 Linux에만 해당됩니다. 그러나 C를 배우는 것과는 반대로 줄 읽기 기능을 사용하려는 경우 웹에서 사용할 수있는 몇 가지 공개 도메인 줄 읽기 기능이 있습니다.
Gilles 'SO- 악마 그만'

11
왜 그렇게해야합니까? 매뉴얼을 읽고, 매번 호출 할 때마다 버퍼가 재 할당되고, 마지막에 해제되어야합니다.
mbaitoff

29
if(line)검사는 불필요하다. 전화 free(NULL)는 본질적으로 아무 문제가 없습니다.
aroth

50
이 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? 아닌가 fpa는 이미 FILE *fopen()다시 발생 FILE *?
회계사 م

1
줄이 특정 길이로 제한되어 있으면 괜찮습니다. 그렇지 않으면 사용 getline하는 것이 좋은 대안입니다. FILE *캐스트가 불필요하다는 데 동의합니다 .
theicfire

불필요한 캐스트를 제거하고 버퍼 길이에 변수를 추가하고 더 명확하게 변경 fp했습니다 filePointer.
Rob

21

당신의에서 readLine기능, 당신은에 대한 포인터 반환 line(첫 번째 문자로, 엄밀히 말하면 포인터를하지만, 차이는 여기에 무관) 배열. 이 변수는 자동 변수이므로 (즉, "스택") 함수가 반환 될 때 메모리가 회수됩니다. printf스택에 자체 물건을 넣었 기 때문에 횡설수설 한 것을 볼 수 있습니다 .

함수에서 동적으로 할당 된 버퍼를 반환해야합니다. 당신은 이미 하나를 가지고 있습니다 lineBuffer. 원하는 길이로 자르기 만하면됩니다.

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

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). */

@Iron : 내 답변에 무언가를 추가했지만 어려움이 무엇인지 확실하지 않아 마크가 표시되지 않을 수 있습니다.
Gilles 'SO- 악마 그만'

@ 철 : 대답은 당신이 그것을 자유롭게하지 않는다는 것입니다. API 문서에서 리턴 된 버퍼가 malloc 's ansd라는 사실을 문서화하여 호출자가 해제해야합니다. 그런 다음 readLine 함수를 사용하는 사람들은 Gilles가 자신의 답변에 추가 한 스 니펫과 유사한 코드를 작성합니다.
JeremyP

15
//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는 그의 함수의 반환 값이 사라 졌다고 설명합니다. 이 문제를 해결하는 것을 보지 못합니다.
hagello

@Hartley 나는 이것이 오래된 의견이라는 것을 알고 있지만 누군가가 자신의 의견을 읽지 않고 루프에서 자유롭게 (줄)하려고하지 않도록 이것을 추가하고 있습니다. 라인에 대한 메모리는 루프가 시작되기 전에 한 번만 할당되므로 루프가 종료 된 후 한 번만 사용 가능해야합니다. 루프 내부에서 선을 해제하려고하면 예기치 않은 결과가 발생합니다. free ()가 포인터를 처리하는 방법에 따라 메모리를 할당 해제하고 포인터가 이전 위치를 가리키는 경우 코드가 작동 할 수 있습니다. 포인터에 다른 값을 할당하면 다른 메모리 섹션을 덮어 씁니다.
alaniane

2
printf (line)가 잘못되었습니다! 이러지 마십시오. 그러면 코드를 문자열 형식의 취약점으로 열어 인쇄 할 내용을 통해 자유롭게 메모리에 직접 읽고 쓸 수 있습니다. % n / % p를 파일에 넣고 포인터를 제어 한 메모리의 파일 (문자열의 문자열) 주소로 다시 가리키면 해당 코드를 실행할 수 있습니다.
oxagast

10

readLine() 정의되지 않은 동작을 일으키는 지역 변수에 대한 포인터를 반환합니다.

당신을 돌아 다니려면 :

  1. 호출자 함수에서 변수를 작성하고 주소를 readLine()
  2. line사용을 위한 메모리 할당 malloc()-이 경우line 에는 영구적입니다
  3. 일반적으로 나쁜 습관이지만 전역 변수를 사용하십시오.


4

예제에 몇 가지 잘못된 점이 있습니다.

  • printfs에 \ n을 추가하는 것을 잊었습니다. 또한 오류 메시지는 stderr로 이동해야합니다.fprintf(stderr, ....
  • (더 크지 않지만) fgetc()보다는을 사용 하는 것이 좋습니다 getc(). getc()매크로 fgetc()입니다, 적절한 기능입니다
  • getc()반환 int그래서는 ch로 선언되어야한다 int. 와의 비교 EOF가 올바르게 처리 되므로 중요 합니다. 일부 8 비트 문자 세트 0xFF는 유효한 문자로 사용 되며 (ISO-LATIN-1이 예일 수 있음) EOF-1은에 0xFF지정된 경우 char입니다.
  • 라인에 잠재적 인 버퍼 오버 플로우가 있습니다

    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으로static char line[MAX_LINE_LENGTH] -> 함수에서 돌아온 후 값을 유지합니다). -> 나쁨, 함수가 재진입되지 않으며 경쟁 조건이 발생할 수 있음-> 두 스레드에서 두 번 호출하면 결과를 덮어 씁니다.
  • malloc()char line []을 호출하고 함수 호출-> 너무 많은 비용이 들며 malloc버퍼를 다른 함수로 해제하는 책임을 위임합니다 (가장 우아한 솔루션은 동일한 함수의 버퍼 를 호출 malloc하고 호출하는 free것입니다)

BTW에서 '명시 적'캐스팅 char*으로는 const char*중복입니다.

btw2, lineBuffer가 필요 하지 않습니다. malloc()그냥 정의 char lineBuffer[128]하면 해제 할 필요가 없습니다.

btw3은 '동적 크기의 스택 배열'(배열을으로 정의 char arrayName[some_nonconstant_variable])을 사용하지 않으며 수행중인 작업을 정확히 모르는 경우 C99에서만 작동합니다.


1
'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 ()를 호출하는 것은 사용자의 책임입니다. 또는 사용자에게 줄 내용을 쓸 메모리 주소를 인수로 전달하도록 요청할 수 있습니다.


불법 행동과 부정확 한 행동에는 차이가 있습니다 ^^.
Phong

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}도 널 터미네이터와 전체 문자 배열을 채 웁니다.
Thu Yein Tun

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

리눅스에 대해서만 이야기합니까, 질문은 C에서 줄을 읽는 방법에 관한 것입니다.
南山 竹

이것은 표준 C 구현을 위해 작동합니다 ( getdelimgetlinePOSIX.1-2008에서 표준화하고, 다른 사람이 페이지에서 언급). fgets또한 리눅스 전용이 아닌 표준 c입니다
theicfire
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.