C에서 파일의 내용을 문자열로 읽는 방법은 무엇입니까?


97

C에서 파일을 열고 그 내용을 문자열 (char *, char [] 등)로 읽는 가장 간단한 방법 (오류 발생 가능성이 가장 낮고 코드 줄이 가장 적지 만 해석하려는 경우)은 무엇입니까?


8
"가장 간단한 방법"과 "최소 오류 발생 가능성"은 종종 서로 반대입니다.
Andy Lester

14
"간단한 방법"과 "최소 오류 발생 가능성"은 실제로 내 책에서 동의어입니다. 예를 들어 C #의 대답은 string s = File.ReadAllText(filename);. 어떻게 더 간단하고 오류가 발생하기 쉬울까요?
Mark Lakata 2014

답변:


146

나는 전체 버퍼를 원시 메모리 청크로 메모리에로드하고 직접 구문 분석을 수행하는 경향이 있습니다. 이렇게하면 여러 플랫폼에서 표준 lib가 수행하는 작업을 가장 잘 제어 할 수 있습니다.

이것은 내가 이것을 위해 사용하는 스텁입니다. fseek, ftell 및 fread의 오류 코드를 확인할 수도 있습니다. (명확성을 위해 생략 됨).

char * buffer = 0;
long length;
FILE * f = fopen (filename, "rb");

if (f)
{
  fseek (f, 0, SEEK_END);
  length = ftell (f);
  fseek (f, 0, SEEK_SET);
  buffer = malloc (length);
  if (buffer)
  {
    fread (buffer, 1, length, f);
  }
  fclose (f);
}

if (buffer)
{
  // start to process your data / extract strings here...
}

3
또한 fread의 반환 값을 확인합니다. 실제로 오류로 인해 전체 파일을 읽지 못할 수도 있기 때문입니다.
freespace

6
rmeador가 말했듯이 fseek는 4GB 이상의 파일에서 실패합니다.
KPexEA

6
진실. 대용량 파일의 경우이 솔루션은 짜증납니다.
Nils Pipenbrinck

33
이것은 랜딩 페이지이므로 fread문자열을 0으로 종료하지 않는다는 점을 지적하고 싶습니다 . 이로 인해 문제가 발생할 수 있습니다.
ivan-k 2014

18
@Manbroski가 말했듯이 버퍼는 '\ 0'종료되어야합니다. 그래서 나는 buffer = malloc (length + 1);fclose 후에 변경 하고 추가 할 것입니다 : buffer[length] = '\0';(Valgrind에 의해 검증 됨)
soywod

26

안타깝게도 OS에 크게 의존하는 또 다른 솔루션은 파일에 대한 메모리 매핑입니다. 일반적으로 읽기 성능과 응용 프로그램보기 및 운영 체제 파일 캐시가 실제 메모리를 공유 할 수 있으므로 메모리 사용 감소 등의 이점이 있습니다.

POSIX 코드는 다음과 같습니다.

int fd = open("filename", O_RDONLY);
int len = lseek(fd, 0, SEEK_END);
void *data = mmap(0, len, PROT_READ, MAP_PRIVATE, fd, 0);

반면 Windows는 조금 더 까다 롭고 불행히도 테스트 할 컴파일러가 없지만 기능은 CreateFileMapping()MapViewOfFile().


3
해당 시스템 호출의 반환 값을 확인하는 것을 잊지 마십시오!
Toby Speight 2018

3
lseek ()를 호출 할 때 int 대신 off_t를 사용해야합니다.
ivan.ukr

1
주어진 시간에 파일의 내용을 메모리에 안정적으로 캡처하는 것이 목표 인 경우 메모리로 읽어 오는 파일이 해당 간격 동안 다른 프로세스에 의해 수정되지 않을 것이라는 확신이없는 한이 솔루션은 사용하지 않아야합니다. 지도가 사용됩니다. 자세한 내용은이 게시물 을 참조하십시오.
user001

13

"문자열로 내용을 읽음"이 파일에 코드 0의 문자가 포함되어 있지 않음을 의미하는 경우 getdelim () 함수를 사용할 수도 있습니다.이 함수는 메모리 블록을 받아 필요한 경우 재 할당하거나 전체 버퍼를 지정된 구분 기호 또는 파일 끝을 만날 때까지 파일을 읽습니다. 전체 파일을 읽으려면 구분 기호로 '\ 0'을 전달하십시오.

이 기능은 GNU C 라이브러리, http://www.gnu.org/software/libc/manual/html_mono/libc.html#index-getdelim-994 에서 사용할 수 있습니다 .

샘플 코드는 다음과 같이 간단 해 보일 수 있습니다.

char* buffer = NULL;
size_t len;
ssize_t bytes_read = getdelim( &buffer, &len, '\0', fp);
if ( bytes_read != -1) {
  /* Success, now the entire file is in the buffer */

1
나는 이것을 전에 사용했습니다! 읽고있는 파일이 텍스트 (\ 0 포함되지 않음)라고 가정하면 매우 잘 작동합니다.
ephemient

좋은! 전체 텍스트 파일을 슬러 핑 할 때 많은 문제를 저장합니다. 이제 구분 문자없이 EOF까지 바이너리 파일 스트림을 읽는 매우 간단한 방법이 있다면!
안토니

6

파일이 텍스트이고 텍스트를 한 줄씩 가져 오려면 가장 쉬운 방법은 fgets ()를 사용하는 것입니다.

char buffer[100];
FILE *fp = fopen("filename", "r");                 // do not use "rb"
while (fgets(buffer, sizeof(buffer), fp)) {
... do something
}
fclose(fp);

6

stdin 또는 파이프와 같은 특수 파일을 읽는 경우 fstat를 사용하여 미리 파일 크기를 가져올 수 없습니다. 또한 바이너리 파일을 읽는 경우 fgets는 포함 된 '\ 0'문자로 인해 문자열 크기 정보를 잃게됩니다. 파일을 읽는 가장 좋은 방법은 read 및 realloc을 사용하는 것입니다.

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

int main () {
    char buf[4096];
    ssize_t n;
    char *str = NULL;
    size_t len = 0;
    while (n = read(STDIN_FILENO, buf, sizeof buf)) {
        if (n < 0) {
            if (errno == EAGAIN)
                continue;
            perror("read");
            break;
        }
        str = realloc(str, len + n + 1);
        memcpy(str + len, buf, n);
        len += n;
        str[len] = '\0';
    }
    printf("%.*s\n", len, str);
    return 0;
}

1
이것은 O (n ^ 2)입니다. 여기서 n은 파일의 길이입니다. 이보다 많은 업 보트를 가진 모든 솔루션은 O (n)입니다. 이 솔루션을 실제로 사용하지 마십시오. 또는 곱셈 증가가있는 수정 된 버전을 사용하십시오.
Clark Gaebel

2
realloc ()은 이전 메모리를 더 큰 새 메모리에 복사하지 않고도 기존 메모리를 새 크기로 확장 할 수 있습니다. malloc ()에 대한 중간 호출이있는 경우에만 메모리를 이동하고이 솔루션을 O (n ^ 2)로 만들어야합니다. 여기에서는 realloc () 호출 사이에 발생하는 malloc () 호출이 없으므로 솔루션이 괜찮을 것입니다.
Jake

2
중간 "buf"에서 복사 할 필요없이 "str"버퍼 (적절한 오프셋 사용)로 직접 읽을 수 있습니다. 그러나이 기술은 일반적으로 파일 내용에 필요한 메모리를 초과 할당합니다. 또한 바이너리 파일을 조심하십시오. printf는 그것들을 올바르게 처리하지 않을 것이고 어쨌든 바이너리를 인쇄하고 싶지 않을 것입니다!
안토니

3

참고 : 이것은 위에서 허용 된 답변을 수정 한 것입니다.

여기에 오류 검사를 완료하는 방법이 있습니다.

파일이 1GiB보다 클 때 종료 할 크기 검사기를 추가했습니다. 프로그램이 너무 많은 램을 사용하고 컴퓨터를 충돌시킬 수있는 문자열에 전체 파일을 넣기 때문에 이렇게했습니다. 그러나 그것에 대해 신경 쓰지 않는다면 코드에서 제거 할 수 있습니다.

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

#define FILE_OK 0
#define FILE_NOT_EXIST 1
#define FILE_TO_LARGE 2
#define FILE_READ_ERROR 3

char * c_read_file(const char * f_name, int * err, size_t * f_size) {
    char * buffer;
    size_t length;
    FILE * f = fopen(f_name, "rb");
    size_t read_length;
    
    if (f) {
        fseek(f, 0, SEEK_END);
        length = ftell(f);
        fseek(f, 0, SEEK_SET);
        
        // 1 GiB; best not to load a whole large file in one string
        if (length > 1073741824) {
            *err = FILE_TO_LARGE;
            
            return NULL;
        }
        
        buffer = (char *)malloc(length + 1);
        
        if (length) {
            read_length = fread(buffer, 1, length, f);
            
            if (length != read_length) {
                 free(buffer);
                 *err = FILE_READ_ERROR;

                 return NULL;
            }
        }
        
        fclose(f);
        
        *err = FILE_OK;
        buffer[length] = '\0';
        *f_size = length;
    }
    else {
        *err = FILE_NOT_EXIST;
        
        return NULL;
    }
    
    return buffer;
}

그리고 오류를 확인하려면 :

int err;
size_t f_size;
char * f_data;

f_data = c_read_file("test.txt", &err, &f_size);

if (err) {
    // process error
}
else {
    // process data
    free(f_data);
}

1
한 가지 질문 :으로 buffer할당 malloc(length +1)한은 (는) 해제되지 않습니다. 이 방법의 소비자가해야 할 일 free()입니까 , 아니면 할당 된 메모리 가 필요하지 않습니까?
Pablosproject

오류가 발생하지 않은 경우 free (f_data); 호출해야합니다. 지적 해 주셔서 감사합니다
Joe Cool

2

glib사용하는 경우 g_file_get_contents 를 사용할 수 있습니다 .

gchar *contents;
GError *err = NULL;

g_file_get_contents ("foo.txt", &contents, NULL, &err);
g_assert ((contents == NULL && err != NULL) || (contents != NULL && err == NULL));
if (err != NULL)
  {
    // Report error to user, and free error
    g_assert (contents == NULL);
    fprintf (stderr, "Unable to read file: %s\n", err->message);
    g_error_free (err);
  }
else
  {
    // Use file contents
    g_assert (contents != NULL);
  }
}

2

위의 답변에서 수정되었습니다.

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

char *readFile(char *filename) {
    FILE *f = fopen(filename, "rt");
    assert(f);
    fseek(f, 0, SEEK_END);
    long length = ftell(f);
    fseek(f, 0, SEEK_SET);
    char *buffer = (char *) malloc(length + 1);
    buffer[length] = '\0';
    fread(buffer, 1, length, f);
    fclose(f);
    return buffer;
}

int main() {
    char *content = readFile("../hello.txt");
    printf("%s", content);
}

이것은 C 코드가 아닙니다. 질문은 C ++로 태그가 지정되지 않았습니다.
Gerhardh

@Gerhardh 9 년 전 내가 편집 할 때 질문에 대한 빠른 응답! 기능 부분은 순수한 C이지만 c에서 실행되지 않을 것입니다.
BaiJiFeiLong

이 고대 질문은 활성 질문의 맨 위에 나열되었습니다. 나는 그것을 찾지 않았다.
Gerhardh

이 코드 메모리 누수, 당신의 다음 malloc 메모리 : 해제하는 것을 잊지 마세요
ericcurtin

1
// Assumes the file exists and will seg. fault otherwise.
const GLchar *load_shader_source(char *filename) {
  FILE *file = fopen(filename, "r");             // open 
  fseek(file, 0L, SEEK_END);                     // find the end
  size_t size = ftell(file);                     // get the size in bytes
  GLchar *shaderSource = calloc(1, size);        // allocate enough bytes
  rewind(file);                                  // go back to file beginning
  fread(shaderSource, size, sizeof(char), file); // read each char into ourblock
  fclose(file);                                  // close the stream
  return shaderSource;
}

null에 대해 아무것도 검사하지 않기 때문에 이것은 매우 조잡한 솔루션입니다.


이것은 디스크 기반 파일에서만 가능합니다. 명명 된 파이프, 표준 입력 또는 네트워크 스트림에 대해서는 실패합니다.
anthony

하, 내가 여기 온 이유도! 그러나 문자열을 null로 종료하거나 glShaderSource선택적으로 걸리는 길이를 반환해야한다고 생각합니다 .
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

0

참조 용으로 여기에있는 답변을 기반으로 자체 버전을 추가하겠습니다. 내 코드는 sizeof (char)를 고려하고 몇 가지 주석을 추가합니다.

// Open the file in read mode.
FILE *file = fopen(file_name, "r");
// Check if there was an error.
if (file == NULL) {
    fprintf(stderr, "Error: Can't open file '%s'.", file_name);
    exit(EXIT_FAILURE);
}
// Get the file length
fseek(file, 0, SEEK_END);
long length = ftell(file);
fseek(file, 0, SEEK_SET);
// Create the string for the file contents.
char *buffer = malloc(sizeof(char) * (length + 1));
buffer[length] = '\0';
// Set the contents of the string.
fread(buffer, sizeof(char), length, file);
// Close the file.
fclose(file);
// Do something with the data.
// ...
// Free the allocated string space.
free(buffer);

0

쉽고 깔끔함 (파일의 내용이 10000 미만이라고 가정) :

void read_whole_file(char fileName[1000], char buffer[10000])
{
    FILE * file = fopen(fileName, "r");
    if(file == NULL)
    {
        puts("File not found");
        exit(1);
    }
    char  c;
    int idx=0;
    while (fscanf(file , "%c" ,&c) == 1)
    {
        buffer[idx] = c;
        idx++;
    }
    buffer[idx] = 0;
}

미리 필요 하다고 생각 하는 모든 메모리를 할당하지 마십시오 . 이것은 나쁜 디자인의 완벽한 예입니다. 가능할 때마다 사용량에 따라 메모리를 할당해야합니다. 파일의 길이가 10,000 바이트라고 예상하고 프로그램이 다른 크기의 파일을 처리 할 수없고 어쨌든 크기를 확인하고 오류가 발생한다고 예상한다면 좋은 디자인이 될 것입니다. C를 올바르게 코딩하는 방법을 정말 배워야합니다.
Jack Giffin
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.