작은숲:위키노트/초보자를 위한 C 언어 강의/파일 입출력

C 언어에서는 파일 입출력를 위해 'FILE'이라는 typedef 된 구조체를 지원한다. 이 'FILE' 구조체를 이용해서 우리는 스트림을 쓸 수 있다. C 소스를 보면서 좀더 알아보도록 하자.

#include <stdio.h>
#include <stdlib.h>
#define MAXLINELEN    74
int main()
{    char buffer[MAXLINELEN + 1];
    int line = 1;
    FILE *stream;          /* 1) stream(FILE *) 변수의 정의 */
    stream = fopen("readme.txt", "r");     /* 2) 파일 open */
    if (stream == NULL) {
        puts("File not found!n");
        exit(1);
    }
    while (!feof(stream)) {
        /* 3) read / write */
        if (fgets(buffer, MAXLINELEN + 1, stream) == NULL) break;
        printf("%3d :  %s", line++, buffer);
    }
    /* 4) File close */
    fclose(stream);
    return 0;
}

위에서 보인 바와 같이 항상 스트림 포인터(파일 포인터)는 'FILE *'가 되어야 한다. 그리고 원하는 일을 다 마친 후에는 파일을 닫아줘야 한다. 열었으면 닫아라!

FILE *stream;

이것이 바로 스트림 포인터(파일 포인터)를 선언해주는 것이다. 파일 입출력을 하기 위해 제일 먼저 해야할 일이 바로 이것이다. 그 다음으로는 원하는 파일을 열어서 위에서 선언해 놓은 스트림 포인터에 연결해야 한다. 그래야 우리가 원하는대로 파일 입출력을 할 수 있다.

stream = fopen("readme.txt", "r");

여기서 사용한 fopen() 함수는 두개의 매개 변수를 갖는다. 첫번째 매개변수는 열고자 하는 파일의 이름이고, 두번째 매개변수는 어떤 '모드(mode)'로 파일을 열 것인가를 결정한다. 이렇게 두개의 매개변수를 이용해서 파일을 열면, fopen() 함수는 열어놓은 스트림 포인터(파일 포인터)를 반환한다. 이것을 받아 위에서 선언해놓은 스트림 포인터에 저장하면 된다. 여기에서 잠깐 파일 모드에 대해 살펴보도록 하자. 파일 모드에는 크게 'w', 'r', 'a' 모드가 있다. 이는 파일을 열 때 어떤 용도로 열 것인가를 결정한다.

  • 'w' 모드는 쓰기(write) 모드로 여는 것이다. 즉, 이 모드로 열게 되면 열어놓은 파일에 쓸 수 있게 된다. 이 모드로 열게 되면 파일에서 읽어올 수는 없다.
  • 'r' 모드는 읽기(read) 모드로 여는 것이다. 파일을 단지 읽기 위해 여는 것이다. 이 모드로 열게 되면 파일에 쓰기는 할 수 없다.
  • 'a' 모드는 추가(append) 모드로 여는 것이다. 위의 쓰기 모드와 같이 파일에 내용을 쓰기 위해 여는 것이다. 하지만 쓰기 모드와는 약간 다르다.

쓰기 모드와 추가 모드에 대해 살펴보면, 일단 열고자 하는 파일이 없을 때는 두 모드가 똑같이 동작한다. 새로운 파일을 만들어서 그 파일에 새로운 내용을 쓰게 만들어 준다. 하지만 이미 그 이름의 파일이 있을 때에는 두 모드가 하는 일이 달라진다. 쓰기 모드에서는 기존에 파일이 있다면 덮어쓴다. 기존에 있던 파일을 지우고 새 파일을 만드는 것과 비슷하다. 따라서 기존에 파일이 있는 경우에는 그 파일의 내용이 없어지게 된다. 반면에 추가 모드로 열 때에는 기존의 파일이 존재하는 경우 그 파일의 끝에 새로운 내용을 추가한다. 따라서 기존 내용은 보존이 되며 그 이후부터 새로운 내용이 추가된다. 이렇게 fopen() 함수를 이용해서 파일을 열었을 때 에러가 발생하는 경우가 생길 수 있다. 읽기 모드로 열었을 때는 만약 열고자 하는 파일이 존재하지 않는다면 에러가 발생하게 된다. 또, 쓰기 모드로 열었을 때 만약 하드 디스크에 여유 공간이 없다면 파일을 쓰기 위해 열지 못한다. 이와 비슷한 경우가 플로피 디스크의 쓰기 방지탭이 설정된 경우, 이 때도 쓰기 모드나 추가 모드로 파일을 열지 못한다. 이렇게 에러가 발생하게 되면 fopen() 함수는 스트림 포인터(파일 포인터)를 반환하는 대신 NULL 포인터를 반환하게 됩니다. 따라서 이 반환값을 검사해서 제대로 파일이 열렸는지를 확인할 수 있다.

FILE *stream;
stream = fopen("readme.txt", "r");
if (stream == NULL) {
    puts("File not found!\n");
    exit(1);
}

이런 식으로 if 문을 이용해서 파일이 제대로 열리지 않은 경우에 적절한 대응 루틴을 수행하도록 해야 한다. 만약 파일이 제대로 열리지 않은 상태라면 그 파일에서 읽어오거나 쓴다는 것은 말이 안 되기 때문이다. 이 루틴은 파일 입출력을 하는 프로그램이라면 모두 들어간다. 항상 저 루틴을 명심하시기 바란다. 파일을 열고 반환되는 스트림 포인터를 검사하는 것! 이는 중요한 문제이다. 이제 제대로 스트림이 열렸다면, 즉 파일이 제대로 열렸다면 이제부터는 우리가 원하는 작업을 하면 된다. 읽거나 쓰거나. 이런 일을 해주는 함수는 우리가 지금까지 사용했던 입출력 함수들의 이름 앞에 'f'가 붙는 함수를 사용한다.

fprintf(), fscanf(), fgets(), fputs(), fgetc(), fputc()

이런 함수들이 기존에 사용하던 입출력 함수들과 다른 점이 있다면, 어떤 스트림에 쓸 것인지를 지정해줘야 하는 것이다.

#define MAXLINELEN    74
char buffer[MAXLINELEN + 1];
FILE *stream;
stream = fopen("readme.txt", "r");
fgets(buffer, MAXLINELEN + 1, stream);

이렇게 fgets() 함수는 gets() 함수는 약간 다르다. 우선 어디에 저장할 것인지 하는 변수 buffer(정확히 말해 배열 buffer)는 같다. 하지만, 마지막에 스트림 포인터 stream을 지정해야 한다. fgets() 함수를 이용해서 gets() 함수를 만들 수 있다. gets() 함수는 이미 열려있는 표준 입력 스트림, 즉 stdin을 통해 입력을 받는 것이다. 그렇다면 fgets() 함수의 스트림 포인터만 stdin으로 지정해주면 된다.

gets(buffer);
fgets(buffer, MAXLINE + 1, stdin);

위의 두 함수는 똑같은 일을 한다. 정확히 말하면 약간 차이는 난다. gets() 함수는 단순히 개행 문자를 만날 때까지 입력을 받지만, fgets() 함수는 개행 문자를 만나거나 혹은 (MAXLINE + 1)개의 글자 읽어들일 때까지만 입력을 받는다. printf() 함수도 마찬가지이다. printf() 함수는 표준 출력 스트림, 즉 stdout으로 출력한다. 따라서 fprintf() 함수를 이용하면 아래와 같다.

printf("%d\n", i);
fprintf(stdout, "%d\n", i);

이렇게 스트림만 지정해주면 두 함수가 하는 일을 똑같다. 이런 식으로 하고자 하는 일을 마친 다음에는 열어놓은 스트림을 닫아줘야 한다. 즉, 열어 놓은 파일을 닫는 것이다.

fclose(stream);

이렇게 fclose() 함수를 이용하면 닫을 수 있다.

이 작은숲 문서의 출처는 위키노트의 위키노트: 문서입니다.