저기 저 파일에 저장되어 있는 데이터를 읽고 싶다.
프로그램상에서 파일에 저장되어 있는 데이터를 참조하길 원한다고(읽기 원한다고) 가정.
이 때 제일먼저 해야 할 일은?
구현한 프로그램과 참조할 데이터가 저장되어 있는 파일 사이에서 데이터가 이동할 수 있는 다리를 놓는 일.
이렇나 데이터 이동의 경로가 되는 다리를 가리켜 '스트림(stream)'
프로그램과 파일 사이에 스트림을 형성해야 데이터를 주고 받을 수 있다.
스트림이라는 것은 운영체제에 의해서 형성되는 소프트웨어적인 상태를 의미하는 것일 뿐, 실제로 위 그림과 같은 형태의 다리가 놓여지는 것은 아니다.
스트림 형성 시,
"파일로부터 데이터를 읽어 들일(파일에 데이터를 쓸) 기본적인 준비가 완료되었구나!"
파일은 운용체제에 의해서 그 구조가 결정되고 관리되는 대상,
파일 뿐만 아니라 스트림의 형성도 운영체제의 몫임을 이거
fopen 함수호출을 통한 파일과의 스트림 형성과 FILE 구조체
스트림을 형성할 때 호출하는 함수 → 이 함수의 호출을 통해서 프로그램상에서 파일과의 스트림을 형성 할 수 있음
#include <stdio.h>
FILE * fopen(const char * filename, const char * mode)
- 성공 시 해당 파일의 FILE 구조체 변수의 주소 값, 실패 시 NULL 포인터 반환
위 함수의 첫 번재 인자로는 스트림을 형성할 파일의 이름, 두 번째 인자로는 형성할 스트림의 종류에 대한 정보를 문자열의 형태로 전달한다.
그러면 이 함수는 해당 파일과의 스트림을 형성하고 스트림 정보를 FILE 구조체 변수에 담아서 그 변수의 주소 값을 반환
fopen 함수의 반환형을 보면, FILE이라는 이름의 기본 자료형이 존재하지 않으니 이는 분명 구조체의 이름이다.
FILE 구조체가 어떻게 정의되어 있는지는 알 필요가 없음.
FILE 구조체 변수의 멤버에 직접 접근할 일이 없기 때문.
FILE 구조체의 포인터는 파일을 가리키기 위한 용도로 사용된다.
즉, 포인터를 이용해서 파일에 데이터를 저장하거나 파일에 저장된 데이터를 읽게 된다.
따라서 FILE 구조체가 어떻게 정의되어 있는지는 알 필요가 없음.
위 그림은 프로그램상에서 fopen 함수를 호출했을 때 일어나는 일들
- fopen 함수가 호출되면 FILE 구조체 변수가 생성된다.
- 생성된 FILE 구조체 변수에는 파일에 대한 정보가 담긴다.
- FILE 구조체의 포인터는 사실상 파일을 가리키는 '지시자'의 역할을 한다.
입력 스트림과 출력 스트림 생성
스트림 : '한 방향으로 흐르는 데이터의 흐름'
때문에 '입력 스트림(파일 읽기)', '출력 스트림(파일 쓰기)'으로 구분 됨
스트림 형성을 위한 fopen 함수의 호출방법
첫 번째 전달 인자 : 스트림을 형성할 파일의 이름
두 번째 전달 인자 : 형성하고자 하는 스트림의 종류
출력 스트림의 형성을 요청하는 fopen 함수의 호출문
FILE * fp = fopen("data.txt","wt"); // 출력 스트림의 형성
"파일 data.txt와 스트림을 형성하되 wt 모드로 스트림을 형성해라!"
위 그림에서 보이는 스트림은 출력 스트림이기 때문에 파일에 데이터는 쓸 수 있어도 읽지는 못한다.
파일로부터 데이터를 읽기 원한다면 다으므이 문장을 통해서 별도로 입력 스트림을 형성
FILE * fp = fopen("data.txt","ㄱt"); // 출력 스트림의 형성
"파일 data.txt와 스트림을 형성하되 rt 모드로 스트림을 형성해라!"
fopen 함수의 호출을 통해서 파일과의 스트림이 형성되었을 떄 '파일이 개방(오픈)되었다' 라고 표현하는 것이 일반적.
파일에 데이터를 써보자
스트림을 형성해서 파일에 데이터를 써보고자 함.
fputc
fputc('A', fp);
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main(void)
{
FILE* fp = fopen("data.txt", "wt");
if (fp == NULL) {
puts("파일오픈 실패!");
return -1; // 비정상적인 종료를 의미 -1 반환
}
fputc('A', fp);
fputc('B', fp);
fputc('C', fp);
fclose(fp); // 스트림의 종료
return 0;
}
절대 경로로 지정하고 싶으면 경로를 포함해서 파일이름을 지정해도 된다.
FILE* fp = fopen("C:\\data.txt", "wt");
fp가 지칭하는 파일 data.txt에 문자 A가 저장된다.
문자 A, B, C를 저장한 다음에 fclose 함수가 호출되면, 데이터는 안정적으로 저장이 되고, data.txt와 연결되었던 출력 스트림은 소멸이 된다.
스트림의 소멸을 요청하는 fclose 함수
fclose 함수는 fopen 함수의 반재 기능 제공.
fopen 함수 : 스트림을 형성 → 파일을 개방
fclose 함수 : 스트림을 해제 → 파일을 닫는 함수
#include <stdio.h>
int fclose(FILE * stream);
fclose 함수의 호출을 통해서 개방되었던 파일을 닫아줘야 하는 이유?
- 운용체제가 할당한 자원의 반환
- 버퍼링 되었던 데이터의 출력
함수의 호출을 통해서 스트림의 형성을 요청하는 것은 우리지만, 실제로 스트림을 형성하는 주체는 운영체제이다.
그리고 운영체제는 스트림의 형성을 위해서 시스템의 자원(주로 메모리)을 할당한다.
만약 파일을 닫아주지 않으면 할당된 채로 남아있어, 그만큼의 자원손실을 초래하기 때문에 파일의 사용이 끝나는 즉시 fclose 함수를 호출해서 자원을 반환해줄 필요가 있다.
또한, 콘솔 스트림의 중간에 존재하는 입력버퍼와 출력버퍼와 같이 파일 스트림의 경우에도 중간에 입력버퍼와 출력버퍼가 존재한다.
위 그림에서 보이듯이 운영체제는 프로그램과 파일 사이에 입출력 버퍼를 둬서 성능의 향상을 도모함
때문에 fputc와 같은 함수의 호출로 데이터를 파일로 전송한다고 해서 파일에 바로 저장되는 것이 아니라, 일단은 출력버퍼에 저장되었다가 운영체제가 정해놓은 버퍼링 방식에 따라서 뒤늦게 파일에 저장이 된다.
그런데 이러한 버퍼링 방식이 지니는 문제점이 하나 있다.
예를 들어, 문자 A와 B가 출력버퍼에 존재하는 상태에서(파일에 저장되기 직전의 상태에서) 컴퓨터의 전원이 꺼졌다고 가정
문자 A, B 는 파일에 저장이 안됨.
그러나 fclose 함수의 호출을 통해서 파일을 닫아주면 출력 버퍼에 저장되어 있던 데이터가 파일로 이동하면서 출력버퍼는 비워지게 됨
때문에 사용이 끝난 파일은 곧바로 fclose 함수를 호출해 주는 것이 좋음
fflush 함수
스트림을 종료하지 않고 버퍼만 비우고 싶을 때에는 다음 fflush 함수를 호출하면 됨
#include <stdio.h>
int fflush(FILE * stream);
- 함수호출 성공 시 O, 실패 시 EOF 반환
입출력 버퍼를 비우는 것과 관련해서 아래 내용들을 확인
- 출력버퍼를 비운다는 것은 출력버퍼에 저장된 데이터를 목적지로 전송
- 입력버퍼를 비운다는 것은 입력버퍼에 저장된 데이터를 소멸
- fflush 함수는 출력버퍼를 비우는 함수이다.
- fflush 함수는 입력버퍼를 대상으로 호출할 수 없다.
위 내용도 파일 스트림에 그대로 적용.
다음과 같이 파일의 출력버퍼를 비울 수 있다. 그러면 출력버퍼에 저장된 데이터가 실제로 파일에 저장된다.
int main(void)
{
FILE * fp = fopen("data.txt", "wt"); // 출력 스트림 생성
.....
fflush(fp); // 출력 버퍼를 비우라는 요청
}
"그럼 파일 스트림의 입력버퍼는 어떻게 비움?"
파일 스트림의 입력버퍼를 비우는 함수는 필요가 없음.
파일에 저장된 데이터는 원할 때 언제든지 읽을 수 있을 뿐만 아니라(읽혀진 데이터는 입력버퍼에서 지워진다)
파일대상의 입력버퍼를 비워야만 하는 상황이라는 것이 특별히 존재하지 않기 때문
파일로부터 데이터를 읽어봄
data.txt를 열어서 그 안에 저장된 문자를 읽어 들이는 프로그램
아래와 같이 호출
int ch = fgetc(fp);
위 함수호출로 인하여 FILE 구조체의 포인터 FP가 지칭하는 파일에 저장된 문자 하나가 반환되여 변수 ch에 저장된다.
예제
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main(void)
{
FILE* fp = fopen("data.txt", "rt");
if (fp == NULL) {
puts("파일오픈 실패!");
return -1; // 비정상적인 종료를 의미 -1 반환
}
for (int i = 0; i < 3; i++)
{
int ch = fgetc(fp);
printf("%c \n", ch);
}
fclose(fp);
return 0;
}
참고 : [윤성우 열혈 C 프로그래밍] - 대부분의 내용 및 코드는 이 책에서 개인 공부 정리 목적으로 참고하였습니다.
'Language&Framework&Etc > C' 카테고리의 다른 글
파일 입출력(24-3) 파일 입출력 함수의 기본 (0) | 2020.12.16 |
---|---|
파일 입출력(24-2) 파일의 개방 모드(Mode) (0) | 2020.12.14 |
구조체와 사용자 정의 자료형2(23-5) 열거형(Enumerated Type)의 정의와 의미 (0) | 2020.12.14 |
구조체와 사용자 정의 자료형2(23-4) 공용체(Union Type)의 정의와 의미 (0) | 2020.12.13 |
구조체와 사용자 정의 자료형2(23-3) 구조체의 유용함에 대한 논의와 중첩 구조체 (0) | 2020.12.13 |