C 프로그래밍 : 파일 입출력

2019. 4. 2. 22:20coding

  1. 파일에 저장된 데이터

    1. 파일에 저장되어 있는 데이터를 참조하기를 원할때는 프로그램과 참조할 데이터가 저장되어 있는 파일 사이에 데이터가 이동할 수 있는 다리를 놓아야 한다.
    2. 프로그램과 파일 사이에 스트림을 형성해야 데이터를 주고받을 수 있다. 스트림이란 운영체제에 의해서 형성되는 소프트웨어적인 상태를 의미한다.
  2. fopen 함수 호출을 통한 파일과의 스트림 형성과 FILE 구조체

     #include <stdio.h>
     FILE * fopen(const char*filename, const char * mode);
     /* 성공 시 해당 파일의 FILE 구조체 변수의 주소값, 실패 시 NULL 포인터 반환
         첫번째 인자로는 스트림을 형성할 파일의 이름, 두번째 인자로는 형성할 스트림의
         종류에 대한 정보를 문자열의 형태로 전달한다. 그러면 이 함수는 해당 파일과의 스트림을
         형성하고, 스트림 정보를 FILE 구조체 변수에 담아서 그 변수의 주소값을 반환한다.*/
    1. fopen 함수가 호출되면 FILE 구조체 변수가 생성된다 → 생성된 FILE 구조체 변수에는 파일에 대한 정보가 담긴다 → FILE 구조체의 포인터는 사실상 파일을 가리키는 '지시자' 의 역할을 한다.
  3. 입력 스트림과 출력 스트림의 생성

    1. 스트림이란 한 방향으로 흐르는 데이터의 흐름을 의미한다. 따라서 스트림은 데이터를 파일로부터 읽어들이기 위한 '입력 스트림' 과 데이터를 파일에 쓰기 위한 '출력 스트림' 으로 구분된다.

    2. 스트림 형성을 위한 fopen 함수를 호출할 때에는 [스트림을 형성할 파일의 이름] 과 [형성하고자 하는 스트림의 종류] 를 인자로 전달하여야 한다.

       FILE * fp = fopen("data.txt", "wt"); //출력 스트림의 형성
       /* 파일 data.txt와 스트림을 형성하되 wt 모드로 스트림을 형성하라
           -> wt 모드의 스트림이란 텍스트 데이터를 쓰기 위한 출력 스트림을 뜻한다.*/
      
       FILE * fp = fopen("data.txt", "rt"); //입력 스트림의 형성
       /* 파일 data.txt와 스트림을 형성하되 rt 모드로 스트림을 형성하라
         -> rt 모드의 스트림이란 텍스트 데이터를 읽기 위한 입력 스트림을 뜻한다 */
    3. fopen 함수의 호출을 통해 파일과의 스트림이 형성되었을 때, 파일이 개방(오픈) 되었다고 표현한다.

  4. 파일에 데이터를 써보기

     FILE * fp = fopen("C:\\Project\\data.txt", "wt"); 
     /* 파일의 경로를 지정하여 해당 디렉토리에 data.txt 파일을 생성한다. 스트림이 형성되면,
     fp는 파일 data.txt를 지정하는 포인터가 된다. */
    
     fputc('A', fp); //fp가 지칭하는 파일 data.txt에 문자 A가 저장된다.
     fclose(fp); //스트림의 종료 -> 데이터가 안정적으로 저장되고, 출력 스트림이 소멸된다.
  5. 스트림의 소멸을 요청하는 fclose 함수

    1. fclose 함수는 스트림을 해제하고 파일을 닫는 함수이다. 성공시 0, 실패시 EOF를 반환한다.
    2. fclose 함수의 호출을 통해서 파일을 닫아주는 이유는 운영체제가 할당한 자원을 반환하고, 버퍼링 되었던 데이터를 출력하기 위함이다.
    3. 함수의 호출을 통해 스트림의 형성을 요청하는 것은 프로그램이지만, 실제로 스트림을 형성하는 주체는 운영체제이다. 그리고 운영체제는 스트림의 형성을 위해서 시스템의 자원 (주로 메모리) 를 할당한다. 이 자원은 파일을 닫아주지 않은면 할당된 채로 남아있게 되어, 그만큼의 자원 손실을 초래하기 때문에 파일의 사용이 끝나는 즉시 fclose 함수를 호출해서 자원을 반환해 줄 필요가 있다.
    4. 운영체제는 프로그램과 파일 사이에 입출력 버퍼를 두어 성능을 향상시킨다. fputc와 같은 함수의 호출 시 데이터가 파일에 바로 저장되는 것이 아니라, 일단은 출력 버퍼에 저장되었다가 운영체제가 정해놓은 버퍼링 방식에 따라 저장된다.
    5. 사용이 끝난 파일은 곧바로 fclose함수를 호출하여 출력버퍼를 비워야 한다.
  6. fflush 함수

    1. fflush 함수는 출력버퍼를 비우는 함수이며 입력버퍼를 대상으로 호출할 수 없다.

    2. 출력버퍼를 비운다는 것은 출력버퍼에 저장된 데이터를 목적지로 전송한다는 의미이며 입력버퍼를 비운다는 것은 입력버퍼에 저장된 데이터를 소멸시킨다는 의미이다.

       int main(void)
       {
           FILE * fp = fopen("data.txt", "wt"); //출력 스트림 형성
           ...
           fflush(fp); //출력 버퍼 비우기 요청
       }
    3. 파일에 저장된 데이터는 원할때 언제든지 읽을 수 있고, 읽혀진 데이터는 입력버퍼에서 지워지기 때문에 입력버퍼를 비울 필요는 없다.

  7. 스트림을 구분하는 기준

    1. r (읽기 가능) : 파일이 없으면 에러
    2. w (쓰기 가능) : 파일이 없으면 생성
    3. a (파일 끝에 덧붙여 쓰기 가능) : 파일이 없으면 생성
  8. 텍스트 파일과 바이너리 파일

    1. 텍스트 파일 : 사람이 인식할 수 있는 문자를 담고 있는 파일 (문자 데이터 파일)
    2. 바이너리 파일 : 컴퓨터가 인식할수 있는 데이터를 담고 있는 파일 (영상, 음원 파일 등)
  9. 텍스트 모드와 바이너리 모드

    1. 파일을 텍스트 모드로 개방하면 운영체제에서 개행 형태의 변환이 자동으로 이루어진다. (윈도우 기준 C프로그램에서 \n을 파일에 저장하면 \r\n으로 변환되어 저장되고, 파일에 저장된 \r\n을 C프로그램 상에서 읽으면 \n으로 변환되어 읽혀진다.)
    2. 텍스트 모드의 파일 개방을 위해서는 fopen 함수의 두번째 인자로 rt, wt, at등 하나를 선택하여 전달한다.
    3. 바이너리 데이터를 저장하고 있는 파일의 경우에는 변환이 일어나면 안되기 때문에 바이너리 모드로 파일을 개방하여 전달하여야 하며, fopen의 두번째 인자로 rb, wb, ab 중 하나를 선택하여 전달해야 한다.
    4. 개방 모드에 t나 b를 붙여서 전달하지 않으면 파일이 텍스트 모드로 개방된다.
  10. 개방된 파일 대상의 데이터 입력 및 출력방법

    int fputc(int c, FILE * stream); //문자 출력
    int fgetc(FILE * stream); //문자 입력
    int fputs(const char * s, FILE * stream); //문자열 출력
    char fgets(char * s, int n, FILE * strema); //문자열 입력
    1. 문자열이 파일에 저장될 때에는 문자열의 끝을 의미하는 널 문자는 저장되지 않는다. 때문에 파일에서는 개행을 기준으로 문자열을 구문한다. 따라서 문자열을 읽어들일 때에는 fgets 함수를 두번 호출하여 개행 문자를 만날때 까지 문자열을 읽어들이고, 한번 더 개행 문자를 읽어들여야 한다. fgets 함수의 호출을 통해 읽어들일 문자의 끝에는 반드시 \n 문자가 존재해야 한다.
  11. feof 함수 기반의 파일 복사 프로그램

    1. feof 함수를 이용하면 인자로 전달된 FILE 구조체의 포인터를 대상으로 더 이상 읽어들일 데이터가 존재하지 않으면 (파일의 끝까지 모두 읽어들인 상태이면) 0이 아닌 값을 반환한다.
  12. 바이너리 데이터의 입출력

    1. fread 함수는 두번째 전달인자와 세번째 전달인자의 곱의 바이트 크기만큼 데이터를 읽어 들이고, 실제로 읽어들인 데이터의 갯수를 반환한다. 함수의 호출은 성공했지만 파일의 끝에 도달해서 모두 읽어들이지 못했거나 오류가 발생하는 경우에는 실제 데이터보다 작은 값이 반환된다.

       #include <stdio.h>
       size_t fread(void * buffer, size_t size, size_t count, FILE * stream);
       // 성공 시 전달인자 count, 실패 또는 파일의 끝 도달 시 count 보다 작은 값 반환.
      
       int buf[12];
       fread((void*)buf, sizeof(int), 12, fp); //fp는 FILE 구조체 포인터
       // sizeof(int) 크기의 데이터 12개를 fp로부터 읽어 들여서 배열 buf에 저장하라
    2. fwrite 함수는 바이너리 데이터의 출력에 사용되며 fread 함수와 유사하다.

       size_t fwirte(const void * buffer, size_t size, size_t count, FILE * stream);
       // 성공 시 전달인자 count, 실패 시 count 보다 작은 값 반환.
      
       int buf[7] = {1, 2, 3, 4, 5, 6, 7};
       fwrite((void*)buf, sizeof(int), 7, fp);
       // sizeof(int) 크기의 데이터 7개를 buf로부터 읽어서 fp에 저장하라
  13. 서식에 따른 데이터 입출력

    1. fprintf 함수를 사용하면 첫번째 전달인자로 FILE 구조체의 포인터가 입력되어 포인터가 지칭하는 파일로 출력이 이루어지고, 두번째 이후의 전달인자를 통해서 만들어진 문자열이 첫번째 전달인자가 가리키는 파일에 저장된다.

    2. fscanf도 마찬가지로 첫번째 인자를 통해 전달된 포인터가 지칭하는 파일로부터 데이터를 읽어들이고 파일의 끝에 도달하거나 오류가 발생하면 EOF를 반환한다.

       char name[10]="홍길동"; //텍스트 데이터
       char sex="M"; //텍스트 데이터
       int age=24; //바이너리 데이터
       fprintf(fp, "%s %c %d", name, sex, age); //fp는 FILE 구조체 포인터
      
       char name[10];
       char sex;
       int age;
       fscanf(fp, "%s %c %d", name, &sex, &age);
  14. 텍스트와 바이너리 데이터의 집합체인 구조체 변수의 입출력

    1. 구조체 변수를 하나의 바이너리 데이터로 인식하고 처리하면 변수를 통채로 저장하고 읽어들일 수 있다. fwrite 함수를 통해서 통째로 저장하고 fread 함수를 통해서 통째로 복원한다.
  15. 파일 위치 지시자

    1. FILE 구조체의 멤버 중에는 파일의 위치 정보를 저장하고 있는 멤버가 있는데, 이 멤버의 값은 fgets, fputs, fread, fwrite 와 같은 함수가 호출될 때 마다 참조 및 갱신된다.
    2. 파일 위치 지시자는 파일이 처음 개방되면 무조건 파일의 맨 앞부분을 가리킨다. 따라서 파일의 중간부분부터 데이터를 읽거나 쓰기를 원한다면 파일위치 지시자를 이동시켜야 한다.
  16. 파일 위치 지시자의 이동

    1. 파일 위치 지시자를 직접 이동시키고자 할때는 fseek 함수를 호출하여 파일 위치 지시자를 원하는 곳으로 이동할 수 있다.

       #include <stdio.h>
       int fseek(FILE * stream, long offset, int wherefrom);
       //성공시 0, 실패시 0이 아닌 값을 반환
       //stream으로 전달된 파일 위치 지시자를 wherefrom에서부터 offset 바이트 만큼 이동시킨다
    2. wherefrom에 전달될 수 있는 상수는 SEEK_SET(0) 에서 SEEK_SET(2) 까지이고, 0은 파일 맨 앞, 1은 현재 위치, 2이면 파일의 맨 끝에서부터 이동을 시작한다.

      1. SEEK_SET 전달 시 첫번째 바이트에서부터 이동을 시작한다.
      2. SEEK_END 전달 시 EOF에서부터 이동을 시작한다.
    3. 매개변수 offset에는 양의 정수나 음의 정수가 전달될 수 있으며, 양의 정수가 전달되면 파일의 마지막을 향해 지시자가 이동하고, 음의 정수가 전달되면 파일의 시작 위치를 향해 이동한다.

  17. 현재 파일 지시자의 위치 확인

    1. 현재 파일 위치 지시자 정보를 확인하고 싶다면 ftell 함수를 호출한다.

       #include <stdio.h>
           long ftell(FILE * stream); //파일 위치 지시자의 위치 정보 반환
    2. ftell 한수는 파일 위치 지시자의 위치 정보를 반환하며, 파일 위치 지시자가 첫번째 바이트를 가리킬 경우 0을 반환하고, 세번째 바이트를 가리키는 경우 2를 반환한다.