C 관련 포스팅 목록
2021.12.01 - [C/stdio] - C 파일 생성 및 스트림 열기(stdio/fopen)
2021.11.30 - [C/stdio] - C 파일 스트림 닫기(stdio/fclose)
2020.07.03 - [C/stdio] - C/C++ printf 포맷 API 사용 예제(출력) - 1
2020.07.05 - [C] - C/C++ Hex 문자열 바이트 변환 예제
2020.07.03 - [C] - C/C++ 문자열 Hex 값 출력(16진수 변환)
C stdio 라이브러리의 fflush 함수를 통해 파일 스트림 버퍼를 실시간 저장하기
안녕하세요.
오늘은 이전 포스팅에서 다뤘던 fopen, fclose 함수의 특징에서 생겨난 fflush 함수에 대해 알아보도록 하겠습니다. 이전에 fopen으로 파일 스트림을 생성하여 데이터를 저장하고 fclose를 하지 않으면 메모리 릭이나 데이터가 제대로 저장되지 않는다고 하였습니다.
제대로 저장되지 않는다는 말은 내가 어떤 파일을 열고 데이터를 저장하고 있는 도중에 다른 프로그램에서 내가 방금 생성한 파일을 열고 데이터를 읽으려고 하면 읽을 수가 없습니다. 스트림을 통해 버퍼를 저장해 나가고 있는 상태인 거지 실제 파일 I/O가 발생하지 않아 기억장치에 데이터가 저장되지는 않습니다.
즉, 메모리에 쌓이고 있지만 저장장치에는 기록되지 않은 상태라는 말이죠.
그렇다면 모든 프로그램들이 반드시 이전 프로그램에서 파일을 fclose 할 때까지 기다려야 한다는 말이 되는데 이건 너무나도 현대 컴퓨터 시대에 비효율적인 방법입니다.
특히 다중 사용자가 한 시스템을 공유해서 사용하는 리눅스 시스템에서는 이런 특징은 많은 문제를 발생시킵니다. 관리자가 생성한 파일을 일반 사용자들이 공유하여 리소스를 읽거나 아니면 수정하는 일이 빈번하게 발생합니다.
따라서 fflush와 같은 함수는 어느 정도 일정한 패턴의 내용이 버퍼에 채워지면 사용자가 임의로 파일에 I/O를 발생시켜 데이터를 저장시키도록 합니다.
여기서 추가로 fflush를 꼭 호출해야만 기억장치에 저장되는 것은 아닙니다. 또한 꼭 fclose를 호출해야만 저장되는 것도 아니죠. 한 가지 특별한 상황에서는 운영체제에서 직접 파일 I/O를 발생시켜 기억장치에 주기적으로 저장합니다. 바로 엄청난 양의 버퍼를 쓸 때입니다.
아까 위에서 설명한 것처럼 임시 버퍼는 메모리 공간에 쓰인다고 하였는데, 메모리 공간은 기억장치 공간보다 가격도 비싸고 공간이 작습니다. 따라서 이런 공간을 임시 데이터 쓰는 용도로 쓰는 것은 아주 많이 비효율적이죠. 따라서 어느 정도의 양의 데이터가 차면 운영체제는 주기적으로 I/O를 발생시킵니다.
자 그러면 fflush 함수와 예제를 알아보겠습니다.
함수 구조
함수가 포함된 헤더
stdio.h
함수원형
int fflush(FILE *stream)
인자
stream: 쓰기 모드로 연 파일 스트림 포인터
반환
0: 스트림 버퍼의 i/o에 성공하면 반환
이외: 실패
주의
*: fflush 함수는 쓰기 모드로 연 파일 스트림에 한해서 사용이 가능합니다. 만약 읽기 모드로 연 경우 실패합니다.
파일을 쓰는 도중에 동시에 파일 읽기 시도 예제
#include <stdio.h>
#include <string.h>
int main()
{
char buf[512];
const char *text = "안녕하세요. 개구리입니다.\n";
FILE *w_file = NULL;
FILE *r_file = NULL;
w_file = fopen("test.txt", "w");
if (!w_file) {
printf("파일 열기 실패\n");
goto onerror;
}
int r = (int)fwrite(text, strlen(text), 1, w_file);
if (r < 1) {
printf("파일 쓰기 실패\n");
goto onerror;
}
// 읽기 전용으로 파일 스트림 새로 생성
r_file = fopen("test.txt", "r");
if (!r_file) {
printf("읽기 전용 파일 열기 실패\n");
goto onerror;
}
r = fread(buf, strlen(text), 1, r_file);
if (r < 1) {
printf("문자열 읽기 실패\n");
goto onerror;
}
printf("읽기 결과 : %s\n", buf);
return 0;
onerror:
if (w_file) fclose(w_file);
if (r_file) fclose(r_file);
return -1;
}
라인 설명
11: fopen 함수를 이용하여 "test.txt" 파일을 'w' 즉, 새로운 파일 생성 및 쓰기 모드로 열어 파일 스트림을 w_file 포인터 변수에 저장합니다.
17: fwrite 함수를 사용하여 w_file에 "안녕하세요. 개구리입니다." 문자열 데이터를 파일에 씁니다.
24: fopen 함수를 다시 호출하여 "test.txt" 파일을 이번엔 'r' 즉, 기존 파일을 읽기 모드로 열어 파일 스트림을 r_file 포인터 변수에 저장합니다.
30: fread 함수를 사용하여 r_file 파일을 읽습니다.
자 이제 위 예제 결과가 어떻게 출력되는지 확인해보겠습니다.
위와 같이 쓰기 모드 fopen과, fwrite, 읽기 모드 fopen 도 모두 성공했지만 fread에 실패하였습니다. 이는 곧 적은 데이터를 썼고 아직 메모리에만 존재하고 I/O가 발생하지 않아 기억장치에 저장되지 않은 상태이기 때문입니다. 이런 상황에서 fread를 해도 의미가 없습니다.
그렇다면 fflush를 사용하여 문제를 해결해보도록 하겠습니다.
fflush를 사용하여 실시간 파일 저장 예제
...
...
// 지금까지 스트림에 쓴 내용을 파일에 실시간 저장
r = fflush(w_file);
if (r) {
printf("파일 플러시 실패\n");
goto onerror;
}
// 읽기 전용으로 파일 스트림 새로 생성
r_file = fopen("test.txt", "r");
if (!r_file) {
printf("읽기 전용 파일 열기 실패\n");
goto onerror;
}
...
...
설명
*: 29번 fread 함수 호출부의 위 쪽에서 fflush 함수를 호출하는 코드를 추가하였습니다. 즉, fwrite 후 fflush로 메모리에 쌓인 데이터를 I/O 하여 기억장치에 있는 실제 파일에 저장합니다.
이제 결과가 어떻게 바뀌었는지 확인해보겠습니다.
마무리
오늘은 파일에 데이터를 쓴 후 I/O를 발생시켜 실제 기억 장치에 저장된 파일에 데이터를 저장하는 방법을 알아보았습니다. fwrite를 호출한다고 해서 무조건 파일에 저장되는 것이 아니라 일단 메모리에 쌓였다가 최적의 상황에서 I/O를 발생시킨다는 것을 확인하였습니다.
관련 글
C 관련 포스팅 목록
2021.12.01 - [C/stdio] - C 파일 생성 및 스트림 열기(stdio/fopen)
2021.11.30 - [C/stdio] - C 파일 스트림 닫기(stdio/fclose)
2020.07.03 - [C/stdio] - C/C++ printf 포맷 API 사용 예제(출력) - 1