C - File I/O

   1. File open + read || write + close

   2. Reading and Writing strings to a file

   3. Reading and Writing binary to a file



들어가기에 앞서

File I/O 함수들을 이해하기 위해선 컴퓨터의 입/출력에 대한 이해가 기반이 되야한다.

입/출력에 대한 모든 내용을 그림으로 살펴보면 아래와 같다.


스트림(Stream)?

입/출력을 배우면 항상 나오는 개념이 바로 스트림이다. 표준 입/출력 장치를 예로들면, 키보드와 모니터는 각각 독립된 장치인데 어떻게 키보드에서 입력을 받은 데이터를 모니터로 출력해줄 수 있는 걸까? 바로 스트림이 둘 사이를 연결시켜놓아기 때문이다. 정확히 말하면 키보드에서 입력받은 데이터는 입력스트림을 통해 프로그램 내에 있는 입력버퍼로 들어간다. 그런 다음 입력버퍼에 있는 데이터가 출력버퍼로 이동하고 출력버퍼에 있는 데이터는 출력스트림을 통해 모니터로 데이터를 흘려보내주게된다.


표준 입/출력 스트림: 표준으로 정해놓은 입/출력 장치(키보드와 모니터)에 대한 스트림을 말한다.

파일 입/출력 스트림: 파일에 대한 입/출력을 하기위해 필요한 파일 입/출력 스트림을 말한다.


Buffer?

컴퓨터 분야에서 말하는 버퍼(Buffer)는 입력받은 데이터(혹은 출력할 데이터)들을 모아서 한 번에 전송하기 위한 공간이다.

물론, 입력받은 데이터를 즉각적으로 출력해줄 수 있지만 성능면에서 비효율적이다.(스트리밍 서비스는 버퍼를 사용하지 않는다.)

/** 사전 지식은 여기까지 **/


1. File open + read || write + close

C언어에서 파일을 열기위해 fopen() 함수가 사용된다.

Syntax: fopen("FileName", "Mode");

FileName은 경로와 같이 적어줘야하며, 경로가 없다면 해당 프로그램이 실행된 실행파일이 있는 곳에서 찾는다.

Mode의 종류는 아래와 같다.

   "r" - 파일 읽기만 가능함

   "w" - 파일 쓰기만 가능함

   "rb" - binary파일의 읽기모드

   "wb" - binary파일의 쓰기모드

#include <stdio.h>

int main(int argc, char **argv) {
    FILE *fp; // Pointer to the file
    char c; // Character variable to read the content of file

    fp = fopen("C:\\Users\\User\\file.txt", "r"); // Opening a file in r mode

    while (1) {
        c = fgetc(fp);
        if (c == EOF) break;
        else printf("%c", c);
    }
    fclose(fp);
    return 0; // C언어에서 return 0는 성공적인 종료를 뜻함.
}

fgetc(fp);를 통해서 파일에 있는 텍스트를 1글자씩 읽어오는데 이때, 파일포인터가 파일의 끝에 도달해서 더 이상 읽을 텍스트가 없다면 EOF(End Of File) 값을 반환한다. 이것을 이용해 유효성 검증을 한다.


2. Reading and Writing strings to a file

다음 예제소스는 파일포인터를 이용해 파일에서 데이터 한 행단위로 읽어와 출력하는 프로그램이다.

예제소스에서 사용된 fgets() 함수는 표준 파일입출력함수로 파일포인터가 한 라인단위로 이동해가며 데이터를 읽어온다. 

#include <stdio.h>

int main(int argc, char **argv) {
    FILE *fp;
    char str[100];

    fp = fopen("C:\\Users\\User\\file.txt", "r");
    if (fp == NULL) 
        puts("Error: Opening a file");

    while (1) {
        if (fgets(str, 10, fp) == NULL) 
            break;
        else
            printf("%s", str);
    }
    fclose(fp);
    return 0;
}

**여기서 한 가지 짚고넘어가야할 사항**

fgets() 함수는 한 행 단위로 파일포인터를 이동해가며 데이터를 읽어온다고했는데, 한 행을 구분짓는건 흔히 사용하는 엔터(Enter)키다.

컴퓨터가 엔터키에 해당하는 데이터를 읽으면 다음 행으로 인식하는데, 여기서 재밌는점은 Windows와 Linux의 개행문자가 다르다는 점이다.

개행에는 2가지 동작이 포함된다.

   1. 커서를 왼쪽 끝으로 이동시킨다. ( 'Carriage Return' 이라고 함. ) --> CR의 코드표기법은 '\r'

   2. 커서를 아래로 내린다. ( 'Line Feed' 라고 함. ) --> LF의 코드표기법은 '\n'

Windows에선 CR+LF (코드로 \r\n)를 개행으로 인식하고, 

Linux에선 LF (코드로 \n)를 개행으로 인식한다.


다음은 파일에 데이터를 write하는 예제소스이다.

#include <stdio.h>

int main(int argc, char **argv) {
    FILE *fp;
    char str[100];

    fp = fopen("C:\\Users\\User\\file.txt", "w");
    if (fp == NULL) 
        puts("Error: Opening a file");

    gets(str); // 데이터를 키보드로부터 입력받는다.
    fputs(str, fp); // 입력받은 데이터를 파일로 출력한다.

    fclose(fp);
    return 0;
}



3. Reading and Writing binary to a file

fopen() 함수로 파일을 열때 모드에 'b'만 추가적으로 적어주면된다.

#include <stdio.h>

int main(int argc, char **argv) {
    FILE *read_fp, *write_fp;
    char ch;

    read_fp = fopen("C:\\Users\\User\\file.txt", "rb");
    if (read_fp == NULL)
        puts("Error: Opening a file");

    write_fp = fopen("C:\\Users\\User\\file2.txt", "wb");
    if (write_fp == NULL) 
        puts("Error: Opening a file");

    while (1) {
        ch = fgetc(read_fp);
        if (ch == EOF)
            break;
        else
            fputc(ch, write_fp);
    }

    fclose(read_fp);
    fclose(write_fp);
    return 0;
}

**한 가지 짚고넘어갈 사항**

텍스트파일로 처리를 하는 것보다, 바이너리파일로 처리를 하는 게 속도가 훨씬 빠르다. 

이유는 바이너리파일로의 처리는 어떠한 변환작업없이 바로 연산을 하기 때문이다.