본문 바로가기

Daylogs/C

C: 파일 입출력

이전 글: 8. 구조체와 typedef에서 계속



파일 입출력

파일에 저장되어 있는 데이터를 참조하길 원하는 경우,
즉 파일을 읽거나 쓰길 원하는 경우 가장 먼저 해야할 일은,
파일 사이에 데이터가 이동할 수 있는 다리를 놓는 일이다.
즉, 프로그램과 파일 사이에 "스트림(stream)"을 형성해야 한다.
스트림은 운영체제에 의해서 형성되는 소프트웨어적인 상태를 의미하는 것일 뿐이기 때문에,
"파일로부터 데이터를 읽거나 쓸 기본적인 준비가 됐구나"라고 이해하면 된다.



파일 열기

FILE * fopen(const char * filename, const char * mode);
--> 성공 시 해당 파일의 FILE 구조체 변수의 주소값, 실패 시 NULL 포인터 반환

FILE 구조체 변수의 멤버에 직접 접근할 일이 없으므로 그 내용은 정확히 알지 않아도 된다.

fopen이 호출되면, 다음과 같이 수행한다.
1. fopen 함수가 호출되면 FILE 구조체 변수가 생성된다.
2. 생성된 FILE 구조체 변수에 파일에 대한 정보가 담긴다.
3. FILE 구조체의 포인터는 사실 상 파일을 가리키는 '지시자'의 역할을 한다.

filename = 스트림을 형성할 파일 이름
mode = 형성하고자 하는 스트림의 종류



파일 닫기 (스트림의 소멸)

int fclose(FILE * stream);
--> 성공 시 0, 실패 시 EOF를 반환

파일 스트림을 해제한다.

fclose를 하는 이유는,
1. 운영체제가 할당된 자원의 반환
2. 버퍼링 되었던 데이터의 출력
을 하기 위함이다.


int fflush(FILE * stream);
--> 성공 시 0, 실패 시 EOF 반환

출력버퍼를 비운다.



파일의 개방 모드(File open mode)

기준
1. 읽기 위한 스트림이냐? 쓰기 위한 스트림이냐?
2. 텍스트 데이터를 위한 스트림이냐? 바이너리 데이터를 위한 스트림이냐?


r : 읽기 가능, 파일 없으면 에러
w : 쓰기 가능, 파일 없으면 생성
a : 파일 끝에 덧붙임, 파일 없으면 생성

+가 붙으면 읽기/쓰기 가능: r+, w+, a+
t가 붙으면 텍스트 스트림: rt, wt, at, r+t, w+t, a+t
b가 붙으면 바이너리 스트림: rb, wb, ab, r+b, w+b, a+b

텍스트 쓰기가 따로 있는 가장 큰 원인은 개행문자 때문.
C언어에서는 개행을 \n으로 표시. 윈도우는 \r\n로, 맥은 \r로, 유닉스 계열은 \n으로 표시.
이런 처리를 위해 텍스트 모드로 개방하는 것임.

파일을 텍스트 모드로 개방하면, 위와 같이 OS에 따라 개행 문자가 자동으로 변환돼 저장되므로,
바이너리 파일을 처리하려고 한다면 명시적으로 바이너리 모드로 파일을 개방해야 함.



파일 입출력 함수

int fputc(int c, FILE * stream); // 문자 출력
int fgetc(FILE * stream); // 문자 입력
int fputs(const char * s, FILE * stream); // 문자열 출력
char * fgets(char * s, int n, FILE * stream); // 문자열 입력


fgets는 개행 문자를 만날 때까지 문자열을 읽어들인다.
따라서 fgets 함수의 호출을 통해서 읽어들인 문자열의 끝에는 반드시 \n 문자가 존재하게 된다.



파일의 마지막인가?

int feof(FILE * stream);
--> 파일 끝에 도달한 경우  0이 아닌 값 반환

파일의 끝에 도달해서 더 이상 읽을 데이터가 없거나 오류가 발생하는 경우 NULL을 반환함.



바이너리 데이터의 입출력

size_t freed(void * buffer, size_t size, size_t count, FILE * stream);
--> 성공 시 전달인자 count, 실패 또는 파일의 끝 도달 시 count보다 작은 값 전달

"size 크기의 데이터 count개를 stream으로부터 읽어들여 배열 buffer에 저장하라"

예: fread((void *)buf, sizeof(int), 12, fp);
// buf 앞에 void형 포인터로 형변환한다.


size_t write(const void * buffer, size_t size, size_t count, FILE * stream);
--> 성공 시 전달인자 count, 실패 시 count보다 작은 값 반환

"size 크기의 데이터 count개를, buffer로부터 읽어서 stream에 저장하라"

예: fwrite((void *)but, sizeof(int), 7, fp);



텍스트와 바이너리 데이터를 동시에 입출력하기

fprintf(fp, "%s %c %d", name, sex, age); 
fscanf(fp, "%s %c %d", name, &sex, &age); // 파일의 끝이나 오류가 발생할 경우 EOF를 반환한다.



구조체 변수의 입출력

구조체 변수를 하나의 바이너리 데이터로 인식하고 처리한다.
fwrite 함수로 통째로 저장하고 fread 함수를 통해서 통째로 복원할 수 있다.

fwrite((void *)&person, sizeof(person), 1, fp);
fread((void *)&person, sizeof(person), 1, fp);



파일 위치 지시자

파일의 위치 정보를 저장하고 있는 멤버

int fseek(FILE * stream, long offset, int wherefrom);
--> 성공 시 0, 실패 시 0이 아닌 값을 반환.

"stream으로 전달된 파일 위치 지시자를, wherefrom에서부터 offset 바이트만큼 이동시켜라."


wherefrom에 전달될 수 있는 상수
SEEK_SET : 0, 파일의 맨 앞
SEEK_CUR : 1, 현재 위치
SEEK_END : 2, 파일의 끝. 마지막 문자가 아니라 EOF 문자인 것에 주의한다.


long ftell(FILE * stream);
--> 파일 위치 지시자의 위치 정보를 반환