제 26강) C언어의 파일 입출력1 |
오늘은 아마 가장 긴 강좌가 될지도 모르는 "파일 입출력"에 대해서 알아보겠습니다.
(입출력 = 입력 + 출력)
C언어에서의 파일 입출력은 정말 중요합니다.
C++언어에서도 성능 때문에 C언어의 파일 입출력을 종종 사용하지요.
1) 파일과의 통신(FILE) |
파일과의 통신을 하기 위해서는 "어떻게 사용할 수 있느냐" 라는 의문을 가져야 합니다.
크게 우리는 2가지 측면에서 접근할 수 있습니다.
① 바이너리(Binary) 적인 접근 ② 텍스트(Text) 적인 접근 |
바이너리적으로 접근을 하게 되면 우리는 모든 바이트를 가지고 프로그램에서 사용할 수 있습니다.
이것은 어떤 시스템에서든지 동일하게 사용이 가능하죠.
텍스트적으로 접근을 하게 되면 텍스트단위를 가지고 프로그램에서 사용할 수 있습니다.
바로 텍스트로 사용하기에 편하지만 "문자 인코딩 방식"에 따라서 시스템마다 다르게 표기될 수 있습니다.
(^z는 파일의 끝을 의미합니다.)
막간의 상식) 파일의 끝 = EOF(End of File) |
EOF는 파일의 끝을 의미합니다. |
막간의 상식) 문장의 끝을 표현(줄바꿈) |
유닉스 계열의 줄바꿈 기호와 윈도우의 줄바꿈 기호는 약간의 차이가 있습니다. 윈도우: \n 유닉스: \r\n 이렇기 때문에 C언어에서는 \n이나 \r\n을 받아들일때는 똑같이 \r\n으로 받아들입니다. 그리고 윈도우 환경에서 내보낼때는 \r\n을 \n으로, 리눅스 환경에서는 \r\n으로 내보냅니다. |
파일과의 입출력을 위한 표준 입출력에 대해서 알아봅시다.
파일과 입출력을 하기 위해서는 파일 포인터인 FILE를 사용합니다.
FILE은 fopen 함수와 같이 사용합니다.
fopen 함수 |
원형: FILE * fopen ( const char * filename, const char * mode );
mode는 해당 파일을 어떤 모드로 열것이냐를 결정합니다.파일을 열기 위한 함수로서 filename의 파일이 제대로 열렸다면 해당 FILE을, 제대로 열리지 않았다면 NULL값을 반환합니다. |
모드 | 의미 |
"r" | 읽기 모드(반드시 파일이 있어야함) |
"w" | 쓰기 모드(파일이 없으면 새로 만듬, 크기는 0) (주의! 파일이 있다면 내부 내용을 전부 지움) |
"a" | 이어쓰기 모드, 파일 맨 끝에 덫붙여서 씀 (파일이 없으면 새로 만듬, 크기는 0) |
"r+" | 읽고 쓰는 모드(Update, 반드시 파일이 있어야함) |
"w+" | 읽고 쓰는 모드(파일이 없으면 새로 만듬) |
"a+" | 읽고 쓰는 모드(이어쓰기 모드, 파일이 없으면 새로 만듬) |
"rb", "wb", "ab", "ab+", "a+b", "wb+", "w+b" | 위의 모드와 동일하지만 바이너리 모드 (유닉스에서는 b를 안넣어도 자동으로 바이너리모드가 됩니다.) |
2) 스트림(Stream) |
파일 입출력을 배우기 전에 "스트림"에 대한 지식이 필요합니다.
프로그램과 어떤 것과의 사이에 입출력을 위해서 연결된 통로를 뜻합니다. (다리(Bridge)라고도 표현합니다.) |
C언어에서 사용하는 표준스트림은 크게 3가지가 있습니다.
stdin: 프로그램으로 들어가는 데이터 입력 스트림(기본: 키보드) stdout: 프로그램이 출력 데이터를 기록하는 스트림(기본: 모니터) stderr: 오류나 진단을 하기위한 스트림(기본: 모니터) |
여기서 보면 스트림은 들어가는 것(in)과 나오는 것(out), 오류 검출용(err)이 나누어져 있습니다.
고로, 스트림은 단방향이라고 할 수 있습니다.
우리가 위에서 배운 fopen을 한 FILE는 위의 스트림에 대입하여 사용할 수 있습니다.
어떤 함수에 stdin이 들어가 있는 인수를 직접 fopen으로 연 파일 스트림에 연결하여 대신 사용할 수 있고,
stdout이 들어가 있는 인수를 연결하여 대신 사용할 수 있게 됩니다.
3) 파일 입출력(fprintf, fscanf, fgets, fputs) |
이제 파일 입출력을 해봅시다.
간단히 입출력에 사용하는 함수들을 살펴봅시다.
원형: int fprintf ( FILE * stream, const char * format, ... );
fprintf는 printf와 비슷합니다.
단지 첫번째 인수에 stream이 오는것만 다르지요.
원형: int fscanf ( FILE * stream, const char * format, ... );
fscanf도 scanf와 비슷합니다.
단지 첫번째 인수에 stream이 오게 됩니다.
원형: char * fgets ( char * str, int num, FILE * stream );
str: 문자열이 저장되어질 공간, num: 문자열의 길이
fgets는 문자열을 stream에서 받아서 num 길이 만큼 str에 저장하게 됩니다.
이때 불러오는 문장은 "한 줄"단위가 됩니다.
원형: int fputs ( const char * str, FILE * stream );
str: 넘길 문자열
str을 해당 stream으로 넘기게됩니다.
위의 파일 입출력에 사용하는 4개의 함수의 stream에 표준 스트림(stdin, stdout)을 사용하면 일반적인 입출력함수와 동일하게 사용할 수 있죠.
이제 위의 함수들을 사용하여봅시다.
// fprintf와 fscanf 함수
#include <stdio.h>
#include <stdlib.h> // exit 함수를 사용하기 위함
int main(void)
{
FILE *fp = fopen("test.txt", "w+");
char out_str[100] = { 0, }; // 파일로 저장할 문자열을 저장할 배열
char in_str[100] = { 0, }; // 파일에서 받아온 문자열을 저장할 배열
if (fp == NULL)
{
fprintf(stdout, "파일을 열지 못했습니다.");
exit(1); // 문제가 발생했을 때 종료에 쓰이는 함수
}
fprintf(stdout, "문장 입력: "); // stdout을 이용하여 printf처럼 사용
fscanf(stdin, "%[^\n]s", out_str); // stdin을 이용하여 scanf처럼 사용
fprintf(fp, "%s", out_str); // 입력된 문자열을 파일에 쓰기
rewind(fp); // 파일의 포인터를 맨 앞으로 되돌리는 함수
fscanf(fp, "%[^\n]s", in_str); // 파일로부터 문자열을 받아옴
fprintf(stdout, "저장된 문장: %s\n", in_str);
fclose(fp); // 파일을 닫는 함수(필수!)
return 0;
}
먼저 fprintf와 fscanf를 사용한 예제입니다.
8번줄에서 파일을 "w+"모드로 엽니다.
(w+모드로 열었기 때문에 12~16번줄이 일어나는 경우는 거의 없습니다만 해당 줄은 파일이 열리지 않았을 때 실행됩니다.)
18~19번줄에서 문장을 입력받습니다.
20번줄에서 입력받은 문장을 우리가 열어놓은 파일에 넣습니다.
21번줄에서 파일의 포인터를 맨 앞으로 되돌립니다.
파일의 포인터와 rewind |
파일을 읽거나 쓰게되면 파일의 포인터가 움직이게 됩니다. 파일 포인터는 즉, 현재 파일의 내용을 가리키고 있는 포인터를 뜻합니다. 파일을 끝까지 읽게 되면 파일의 포인터는 해당 파일의 끝부분에 위치하게 되고, 파일의 중간에 무언가를 삽입하게 되면 그 삽입된 바로 다음 지역으로 포인터가 이동하게 됩니다. 위의 상황에서는 파일을 "w+"를 사용하여 새로 내용을 기입하였습니다. 그러므로 파일 포인터는 맨 뒤에 위치하게 됩니다. 파일에 들어있는 내용을 읽기 위해서 다시 포인터를 맨앞으로 돌려야합니다. 이럴때 사용하는 함수가 바로 rewind입니다. |
22번줄에서 파일에 저장되어 있는 내용을 in_str에 저장합니다.
23번줄에서 불러온 내용을 출력합니다.
25번줄에서는 이제 파일을 다 읽고 행위가 끝났기 때문에 파일을 닫아줍니다.
fclose 함수 |
파일을 닫는 함수입니다. 동적 배열을 선언한 후에 동적으로 선언한 배열을 free함수로 제거해주듯이 파일을 열면 꼭 fclose로 닫아주어야합니다. |
프로그램 실행과 동시에
파일이 0의 크기로 생성됩니다.(w+모드로 열었기 때문이죠)
그리고 내용을 입력하고 엔터를 누르면
파일의 내용이 저장됩니다.
// fgets와 fputs 함수
#include <stdio.h>
#include <stdlib.h> // exit 함수를 사용하기 위함
int main(void)
{
FILE *fp = fopen("test.txt", "w+");
char out_str[100] = { 0, }; // 파일로 저장할 문자열을 저장할 배열
char in_str[100] = { 0, }; // 파일에서 받아온 문자열을 저장할 배열
if (fp == NULL)
{
fputs("파일을 열지 못했습니다.", stdout);
exit(1); // 문제가 발생했을 때 종료에 쓰이는 함수
}
fputs("문장 입력: ", stdout);
fgets(out_str, 100, stdin); // 문자열 입력
fputs(out_str, fp); // 입력된 문자열을 파일에 쓰기
rewind(fp); // 파일의 포인터를 맨 앞으로 되돌리는 함수
fgets(in_str, 100, fp); // 파일로부터 문자열을 받아옴
fprintf(stdout, "저장된 문장: %s\n", in_str);
fclose(fp); // 파일을 닫는 함수(필수!)
return 0;
}
(실행 결과는 fprintf, fscanf 예제와 같습니다.)
fgets와 fputs도 비슷한 맥락입니다.
다만 fgets에는 "불러와서 저장할 길이"를 따로 정해줄 수 있습니다.
(이때 길이는 불러와서 저장되어 질 배열의 길이보다 적거나 같아야합니다.)
그렇기 때문에 "오버플로우"를 방지할 수 있습니다.
부록) 파일의 내용이 저장되는 시점 |
위의 두 예제 모두 실행과 동시에 파일이 새로 생성됩니다.
이때 파일을 눌러보면 내용은 없습니다.
그리고 프로그램이 종료됨과 동시에 파일의 내용이 채워집니다.
파일의 내용은 임시적으로 해당 스트림의 "버퍼"라는 공간에 머물게 됩니다.
그리고 이 버퍼라는 공간의 내용은 "강제 명령(버퍼를 비워라)", "버퍼가 꽉찼을 때", "파일을 닫을 때(fclose)" 비워집니다.
이렇게 비워지면 연결된 스트림으로 데이터는 이동합니다.
이렇게 버퍼를 지우는 함수를 fflush라고 합니다.
#include <stdio.h>
#include <stdlib.h> // exit 함수를 위함
#include <Windows.h> // system 함수를 위함
#include <string.h> // memset 함수를 위함
#define MAX 100
int main(void)
{
FILE *fp = fopen("test.txt", "w+");
char str[MAX] = { 0, };
if (fp == NULL)
{
fputs("파일을 열지 못했습니다.", stdout);
exit(1); // 문제가 발생했을 때 종료에 쓰이는 함수
}
printf("1문장 입력: ");
scanf("%[^\n]s", str);
while (getchar() != '\n');
fputs(str, fp);
fputc('\n', fp); // 개행을 넣습니다.
system("pause"); // 잠깐 멈춥니다.(Press any key)
memset(str, 0, MAX); // str 문자열을 MAX 길이만큼 0으로 채웁니다.
printf("2문장 입력: ");
scanf("%[^\n]s", str);
while (getchar() != '\n');
fputs(str, fp);
fputc('\n', fp);
system("pause");
printf("버퍼 비우기!!\n");
fflush(fp); // fp의 버퍼를 비웁니다.
// 고로 이때까지 버퍼에 있던 내용이 파일에 저장됩니다.
system("pause");
memset(str, 0, MAX);
printf("3문장 입력: ");
scanf("%[^\n]s", str);
while (getchar() != '\n');
fputs(str, fp);
fputc('\n', fp);
system("pause");
printf("파일 닫기!!\n");
fclose(fp); // 파일을 닫습니다.
return 0;
}
파일의 버퍼가 비워저 저장되는 시점을 보여주는 예제입니다.
memset 함수 |
원형: void * memset ( void * ptr, int value, size_t num );
ptr 배열을 value라는 값으로 num만큼 초기화 시킵니다.메모리를 설정해주는 함수입니다. |
fputc 함수 |
원형: int fputc ( int character, FILE * stream );
fputs 함수는 한 문장을 넣을 때, fputc 함수는 한 문자를 넣을 때 사용합니다. |
위의 프로그램을 실행시키면서 파일을 확인하여 봅시다.
처음 실행 함과 동시에 파일이 생성됩니다.
첫번째 문장을 넣었습니다.
하지만 아직 아무 내용도 들어있지 않습니다.
두번째 문장을 넣었습니다.
여전히 아무 내용도 들어있지 않습니다.
이제 34번줄의 fflush가 작동하였습니다.
fp의 버퍼가 비워지면서 파일에 이때까지 버퍼에 쌓였던 내용들이 들어갔습니다.
세번째 문장을 넣었으나 아직 버퍼가 비워지지 않아 파일에는 적용되지 않습니다.
파일이 닫히면서 버퍼가 비워져서 파일에 나머지 내용이 들어갔습니다.
다음 시간에는 |
파일 입출력 2로 뵙겠습니다.
'Study > C언어' 카테고리의 다른 글
처음하시는 분들을 위한 C언어 기초강의 시즌2 - 28 [전처리기(include, define, ifdef)] (0) | 2018.10.28 |
---|---|
처음하시는 분들을 위한 C언어 기초강의 시즌2 - 27 [C언어의 파일 입출력2] (0) | 2018.10.28 |
처음하시는 분들을 위한 C언어 기초강의 시즌2 - 23 [메모리 구조와 메모리 할당, 배열 동적할당(malloc, realloc, calloc, free)] (0) | 2018.10.28 |
처음하시는 분들을 위한 C언어 기초강의 시즌2 - 24 [구조체와 형식 정의 지정자(struct, typedef)] (0) | 2018.10.28 |
처음하시는 분들을 위한 C언어 기초강의 시즌2 - 25 [공용체와 열거형(union, enum)] (0) | 2018.10.28 |