C - File input/output
[TOC]
스트림이란?
- 프로그램과 외부 입출력 장치 사이에서 데이터를 이동시켜주는 매개체
- 한 방향으로 흐르는 데이터의 흐름
- 입출력 스트림도 입력, 출력 스트림이 구분되어 한 방향으로만 데이터가 흐름
- 스트림은 운영체제가 관리
- 파일 입출력 스트림
- 프로그램이 직접 운영체제에 요청
- 콘솔 입출력 스트림
- 프로그램이 실행되면 자동으로 생성, 종료되면 자동으로 소멸
- 파일 입출력 스트림
- 표준 스트림
- stdin
- 표준 입력 스트림, 키보드 대상으로 입력
- stdout
- 표준 출력 스트림, 모니터 대상으로 출력
- stderr
- 표준 에러 스트림, 모니터 대상으로 출력
- stdout과 동일하나 용도를 구분하여 사용한다.
- stdin
입력과 출력
- 입력
- 프로그램을 기준으로 데이터가 들어오는 것
- 입력 장치
- 키보드
- 마우스
- 파일
- 출력
- 프로그램을 기준으로 데이터가 나가는 것
- 출력 장치
- 모니터
- 프린터
- 파일
스트림의 존재 이유
- 출력 데이터는 출력 버퍼에 저장했다가 나중에 파일에 저장됨으로 인해 성능을 올릴 수 있다.
표준 입출력 함수
- ANCI C의 표준에서 정의된 함수
문자 단위 입출력 함수
문자 출력 함수
putchar()
int putchar(int c);
- 문자를 stdout 표준 출력 스트림으로 전송
- 성공 시 문자, 실패 시 EOF 반환
fputc()
int fputc(int c, FILE *stream);
- 문자를 두 번째 인자에 지정하는 스트림으로 전송
- 지정하는 스트림은 stdout, FILE 등을 지정할 수 있다.
- stdout을 지정한다면 putchar()와 동일하다.
- 성공 시 문자, 실패 시 EOF 반환
문자 입력 함수
getchar()
int getchar(void);
- stdin 표준 입력 스트림으로부터 하나의 문자를 입력 받아서 반환
- 파일의 끝에 도달하거나 함수호출 실패 시 EOF 반환
fgetc()
int fgetc(FILE *stream);
- 인자로 지정한 스트림에서 하나의 문자를 입력 받아서 반환
- 파일의 끝에 도달하거나 함수호출 실패 시 EOF 반환
EOF(WEOF)
- End Of File
- 파일을 끝을 표현하기 위해 정의한 상수 : -1
- 파일의 끝에 도달해서 더 이상 읽을 내용이 없다는 뜻
문자 입력 함수의 반환형이 char가 아니라 int형인 이유
- 문자 반환 시 에러가 나면 EOF를 반환하는데 EOF는 -1로 정의되어있다.
- 컴파일러에 따라 char 형을 unsigned char로 처리할 수 있다.
- 위 두가지 이유로 인해 char형으로 반환한다면 -1이 unsigned로 변환되어 엉뚱한 값이 반환될 수 있기 때문에 char형이 아니라 int형으로 받아서 -1이 반환되도록 한다.
문자열 단위 입출력 함수
문자열 출력 함수
puts()
int puts(const char *s);
- 문자열을 표준 출력 함수 stdout를 통해 출력
- 문자열 출력 후 자동으로 개행
- 성공 시 0이 아닌 값, 실패 시 EOF 반환
fputs()
int fputs(const char *s, FILE *stream);
- 문자열을 두 번째 인자로 지정한 스트림을 통해 출력
- 문자열 출력 후 자동으로 개행하지 않음
- 성공 시 0이 아닌 값, 실패 시 EOF 반환
문자열 입력 함수
gets()
char * gets(char *s);
- 입력 스트림에 있는 문자열을 버퍼에 저장
- 입력 스트림에 버퍼보다 더 긴 문자열이 있는 경우 메모리 침범 에러
- 엔터까지 입력 문자로 취급
- 엔터까지 문자에 포함시키므로 버퍼 크기보다 1작에 입력하고 엔터를 눌러야 입력 문자열이 버퍼 크기를 넘지 않음
- 성공시 버퍼의 포인터 반환
- 실패 시 NULL 포인터 반환
- 파일의 끝에 도달하거나 함수호출 실패
fgets()
char * fgets(char *s, int n, FILE *stream);
- 입력 스트림에 있는 문자열을 버퍼에 저장
- 엔터까지 입력 문자로 취급
- \n(엔터)를 만날 때까지 문자열을 읽기 때문에 공백문자를 입력 할 수 있음
- scanf()는 공백 문자를 입력 종료로 취급하기 때문에 불가
- 버퍼보다 큰 문자열이 있는 경우
- 버퍼 크기를 입력하기 때문에 메모리 침범 에러 안남
- 입력 스트림의 마지막 읽은 곳을 기억하고 있기 때문에 마지막 읽은 곳 다음부터 다시 읽어들일 수 있음
- 파일의 끝에 도달하거나 함수호출 실패 시 NULL 포인터 반환
문자 입출력에서의 EOF
키보드 입력 스트림에서의 EOF
- 함수 호출의 실패
- Windows에서 ctrl + z 키, Linux에서 ctrl + d 키가 입력되는 경우
버퍼
- buffer
- 완충기, 완충 기억 장치, 완화하다
메모리 버퍼
- 프로그램에서 출력 장치까지 데이터가 이동할 때 중간에 데이터를 임시로 모아두는 메모리 공간
키보드의 입력이 입력 버퍼로 들어가는 시점
- 엔터키를 누르는 시점
- 따라서 엔터키를 누르지 않으면 문자열을 입력해도 입력 버퍼로 들어가지 않는다.
버퍼링을 하는 이유
- 버퍼링(buffering)
- 데이터를 임시 저장하는 것
- 이유
- 입력될 때마다 목적지로 전송하는 것보다 한데 묶어 이동시키는 것이 효율적이다.
출력 버퍼를 비우는 fflush 함수
- 인자로 전달되는 출력 스트림의 버퍼를 비운다.
- 출력버퍼에 저장된 데이터가 버퍼를 떠나서 목적지로 이동됨
#include <stdio.h>
int fflush(FILE *Stream);
입력버퍼를 비우기
- fflush() 함수는 출력 스트림을 비우는 함수이므로 입력 스트림에 대해선 C언어 표준에 정의되어있지 않다.
- 컴파일러에 따라 입력 버퍼를 비울 수도 아닐 수도 있다.
- Windows 컴파일러는 비워준다.
- 따라서 범용적으로 입력 스트림 버퍼를 비워주려면 다음과 같이 현재 입력 스트림 버퍼에 있는 데이터를 모두 읽어버리면 된다.
- 입력 스트림에서 읽으면 입력 스트림 버퍼에서 지워지기 때문
while(getchar() != '\n');
파일 스트림
읽기/쓰기 모드
| 모드 | 스트림의 성격 | 파일이 없으면? |
|---|---|---|
| r(read) | 읽기 가능 | 에러 |
| w(write) | 쓰기 가능 | 생성 |
| a(append) | 파일의 끝에 덧붙여 쓰기 가능 | 생성 |
| r+ | 읽기/쓰기 가능(파일이 존재할 경우 지우지 않는다) | 에러 |
| w+ | 읽기/쓰기 가능(파일이 존재할 경우 모두 지운다) | 생성 |
| a+ | 읽기/덧붙여 쓰기 가능 | 생성 |
텍스트 / 바이너리 파일
- 텍스트
- 사람이 읽을 수 있는 텍스트가 저장된 파일
- 바이터리
- 0과 1로 저장된 파일
텍스트 파일의 개행문자
- 개행
- 줄이 바뀜을 나타냄
- 개행문자
- 줄이 바뀜을 나타내는 문자
운영체제 별 개행문자
- C언어는 개행을 ‘\n’으로 나타내므로 파일을 저장할 때 운영체제별로 다르게 저장해야한다.
| 운영체제 | 개행문자 |
|---|---|
| Windows | \r\n |
| Mac | \r |
| Unix | \n |
개행문자의 자동변환
- 파일을 텍스트모드로 열면 개행문자를 자동으로 변환해준다.
- C 프로그램에서 파일에 저장하면 \r\n으로 변환되어 저장
- 파일에 저장된 \r\n을 C 프로그램에서 읽으면 \n으로 변환되어 읽힘
- 읽기/쓰기 모드에 t를 붙여준다.
- 기본값은 t이다.
바이너리 모드
- 파일을 열고/저장할 때 이진 데이터를 있는 그대로 저장한다.
- 텍스트 모드처럼 운영체제에 따라 다르게 읽고/쓰는 부분이 없다.
파일 스트림 형성
- fopen() 함수를 통해서 스트림 형성
- 파일과의 스트림을 형성하고 스트림 정보를 FILE 구조체 변수에 담아서 주소를 반환
- 반환된 FILE 구조체에는 파일에 대한 정보가 담긴다.
- FILE 구조체의 포인터는 사실상 파일을 가리키는 ‘지시자’의 역할을 한다.
#include <stdio.h>
/*
const char *filename : 스트림을 형성할 파일 이름
const char *mode : 형성하고자 하는 스트림의 종류
*/
FILE *fopen(const char *filename, const char *mode);
출력 스트림
- 어플리케이션을 기준으로 데이터를 내보내는 스트림
- 출력 스트림을 생성하면 파일이 생성된다.
- 파일에 데이터 쓰기
fopen("filename", "wt" /*텍스트 쓰기 전용 출력 스트림*/);
입력 스트림
- 어플리케이션을 기준으로 데이터를 가져오는 스트림
fopen("filename", "rt"/*텍스트 읽기 전용 입력 스트림*/);
파일 입출력 함수
- 표준 입출력 함수의 스트림 인자에 파일 스트림을 넘겨준다.
- 사용방법은 표준 입출력 함수와 동일하다.
출력 스트림에 데이터 쓰기
fputc()
fputc('A'/*int 값을 입력받으므로 문자열은 안된다*/, file);
todo 읽을 때마다 포인터 이동
파일의 끝인지 확인
feof()
- 파일의 끝일 경우 0이 아닌 값을 반환
- fgetc, fgets 함수는 파일의 끝에 도달하거나 오류가 발생시 EOF를 반환하므로 파일의 끝을 판단하기 위해서 feof 함수를 사용해야한다.
#include <stdio.h>
int feof(FILE *stream);
바이너리 데이터 입출력
fwrite() - 바이너리 데이터 출력
#include <stdio.h>
/*
const void *buffer : 기록할 데이터 포인터
size_t size : 데이터 단위(데이터 형의 크기)
size_t count : 한 번에 기록할 데이터 개수
FILE *stream : 출력할 파일 스트림
return : 성공 시 count, 실패 시 count보다 작은 값
*/
size_t fwrite(const void *buffer, size_t size, size_t count, FILE *stream);
cout << "binary file output" << endl;
{
FILE *pFile; // 파일 구조체 포인터 선언
fopen_s(&pFile, "fopen_binary.bin", "wb"); // 파일 스트림 얻기
for (size_t i = 0; i < NumCount; i++)
{
int buf = i;
fwrite(&buf, sizeof(size_t), 1, pFile); // 파일에 int 데이터를 바이너리로 기록
}
fclose(pFile); // 파일 스트림 닫기
}
fread() - 바이너리 데이터 입력
#include <stdio.h>
/*
void *buffer : 데이터 저장할 버퍼
size_t size : 데이터 단위(데이터 형의 크기)
size_t count : 한번에 읽을 데이터 개수
FILE *stream : 입력할 파일 스트림
return : 성공 시 count, 실패 시 count보다 작은 값
*/
size_t fread(void *buffer, size_t size, size_t count, FILE *stream);
cout << "binary file input" << endl;
{
FILE *pFile; // 파일 구조체 포인터 선언
fopen_s(&pFile, "fopen_binary.bin", "rb"); // 파일 입력 스트림 얻기
int buf[NumCount] = {}; // 읽은 데이터를 저장할 버퍼
fread(buf, sizeof(size_t), NumCount, pFile); // 파일의 바이너리를 size_t 크기로 읽기
for (size_t i = 0; i < NumCount; i++)
{
cout << buf[i]; // 읽은 데이터를 하나씩 출력
}
cout << endl;
fclose(pFile);
}
바이너리 파일 복사
cout << "mp3 file copy" << endl;
{
FILE *pReadFile;
FILE *pWriteFile;
fopen_s(&pReadFile, "SampleAudio.mp3", "rb");
fopen_s(&pWriteFile, "SampleAudio_Copy.mp3", "wb");
size_t bufferSize = 1;
size_t elementSize = sizeof(unsigned char);
size_t elementCount = 1;
for (size_t i = 0; i < 500000; i++) // 434KB(443,926 bytes) 파일을 복사
{
unsigned char buf;
size_t result = fread_s(&buf, bufferSize, elementSize, elementCount, pReadFile);
// 입력 파일 스트림의 끝인지 검사
// result가 elementCount보다 작아도 에러 때문일 수 있으므로 feof로 끝인지 검사한다.
if (result < 1 && feof(pReadFile))
{
break;
}
fwrite(&buf, elementSize, elementCount, pWriteFile); // 입력 스트림에서 가져온 데이터를 출력 스트림에 쓴다.
}
// 파일 스트림 정리
fclose(pReadFile);
fclose(pWriteFile);
}
파일 스트림 버퍼 비우기
- 스트림을 종료하지 않고 버퍼만 비우는 경우
- 출력버퍼
- 출력버퍼에 저장된 데이터를 목적지로 전송
- 출력버퍼를 비우는 함수
- fflush()
- 입력버퍼
- 입력버퍼에 저장된 데이터를 소멸
- 입력버퍼를 비우는 함수
- 없음
// 리턴 : 성공 시 0, 실패 시 EOF 반환
fflush(file);
파일 스트림 닫기
fclose()
- 스트림의 소멸을 요청
- 스트림에 입력된 데이터가 파일에 저장되고 소멸된다.
- 파일 스트림을 닫아줘야하는 이유
- 운영체제가 할당한 자원의 반환
- 버퍼링 되었던 데이터의 출력
// 리턴 : 성공 시 0, 실패 시 EOF 반환
fclose(file);
텍스트와 바이너리 데이터를 파일에 저장하기
fprintf(), fscanf()
- fprintf(), fscanf()를 통해 형식을 지정하여 텍스트로 파일에 데이터를 출력/입력한다.
cout << "=== format file output" << endl;
{
// open file stream
FILE *file;
fopen_s(&file, "formatFile.txt", "wt");
// output text
fprintf_s(file, "string: %s ,int : %d", "hihi", 99);
// close file
fclose(file);
}
cout << "=== format file input" << endl;
{
// open file stream
FILE *file;
fopen_s(&file, "formatFile.txt", "rt");
// input text
char str[99];
int num;
// 스페이스 이전 문자까지 %s에 지정한 변수에 넣고 \n을 덧붙인다.
fscanf_s(file, "string: %s ,int : %d", str, _countof(str)/*string의 크기*/, &num);
cout << str << endl;
cout << num << endl;
fclose(file);
}
fwrite(), fread()
- 구조체를 바이너리로 저장하고 복원한다.
cout << "=== struct output" << endl;
{
A a;
a.a = 99;
strcpy_s(a.b, sizeof(a.b), "abcd");
FILE *file;
fopen_s(&file, "structFile.bin", "wb");
fwrite(&a, sizeof(a), 1, file);
fclose(file);
}
cout << "=== struct input" << endl;
{
A a = {};
FILE *file;
fopen_s(&file, "structFile.bin", "rb");
fread_s(&a, sizeof(a), sizeof(a), 1, file);
fclose(file);
cout << a.a << endl;
cout << a.b << endl;
}
파일 위치 지시자
- 데이터를 읽고 쓸 위치를 저장하고 있는 멤버를 가리킨다.
fseek()
- 파일 위치 지시자를 이동시킨다.
#include <stdio.h>
/*
FILE *stream : 파일 스트림
long offset : 이동 시킬 바이트 수
int wherefrom : 이동을 시작할 위치
*/
int fseek(FILE *stream, long offset, int wherefrom);
- wherefrom에서 지정한 곳부터 offset 만큼 이동시킨다.
- wherefrom
- SEEK_SET : 파일 맨 앞
- SEEK_CUR : 현재 위치
- SEEK_END : 파일 끝
- 끝 데이터가 아니라 EOF를 가리킨다.
cout << "=== fseek() output" << endl;
{
FILE *file = nullptr;
fopen_s(&file, "fseek.txt", "wt");
fputs("123456789", file);
fclose(file);
}
cout << "=== fseek() input" << endl;
{
FILE *file = nullptr;
fopen_s(&file, "fseek.txt", "rt");
fseek(file, 2, SEEK_END);
cout << fgetc(file) << endl; // -1 : 파일의 끝을 넘어서 이동했기 때문에 -1이 출력
fseek(file, -2, SEEK_END);
char c = fgetc(file);
cout << c << endl; // 8 : 파일의 끝(eof)에서 2칸 뒤로 이동해서 8을 가리킨다.
// fgetc()로 8을 출력한 후에는 9를 가리킨다.
cout << fgetc(file) << endl; // 57 : 9에 해당하는 아스키코드
// fgetc()는 아스키코드를 반환하기 때문에 바로 출력하면 숫자가 출력된다.
// putchar(fgetc(file)); // putchar()는 아스키코드를 값을 받아 표준 출력 스트림에 출력한다.
fseek(file, 2, SEEK_SET);
putchar(fgetc(file)); // 3 : 파일의 처음(1)에서 2칸 이동해서 3을 지정한다.
putchar('\n');
// 3을 출력한 후에는 4를 가리킨다.
fseek(file, 1, SEEK_CUR);
putchar(fgetc(file)); // 5 : 현재 위치 4에서 한 칸이동하니 5를 가리킨다.
putchar('\n');
fclose(file);
}
ftell()
- 현재 파일 위치 반환
#include <stdio.h>
/*
반환값 : 현재 파일 위치 지시자의 위치, 0부터 시작한다.
*/
long ftell(FILE *stream);
// 파일 저장하고 부분적으로 변경하기
cout << "=== ftell() output" << endl;
{
FILE *file = nullptr;
fopen_s(&file, "ftell.txt", "wt");
fputs("00100", file);
fclose(file);
}
cout << "=== ftell() input" << endl;
{
FILE *file = nullptr;
fopen_s(&file, "ftell.txt", "r+t"); // r+모드로 열어야 기존 내용을 유지할 수 있다.
fseek(file, 2, SEEK_CUR); // 처음에서 2칸 이동으로 1을 가리킴
int pos = ftell(file);
cout << pos << endl; // 2 : 0부터 시작
fputc('9', file); // 1을 9변경하여 저장
fclose(file);
file = nullptr;
// 파일 다시 오픈
fopen_s(&file, "ftell.txt", "rt");
char str[10] = {};
fgets(str, 10, file);
fclose(file);
cout << str << endl; // 00900
}
와
- C에서 printf, scanf 등의 함수를 사용하기 위해 필요
- C++에서 cout, cin 객체를 사용하기 위해 필요
- C++의 하위 호환을 위해 stdio.h의 기능도 포함한다.
헤더
- PreCompiled Header
파일 관련 C++ 클래스
- ifstream
- ofstream
경로
절대경로
- Root 디렉터리부터 모두 적은 것
상대경로
- 특정 경로를 기준으로 경로를 표시하는 것
- . : 현재 디렉터리
- .. : 부모 디렉터리
작업경로
- 프로그램에서 경로 작업을 하기 위해 기준으로 설정한 경로
- GetCurrentDirectory()로 알 수 있음.
파일 읽기 함수의 속도 비교
- getLine(0.51ms) « fgets(0.15ms) < fread(0.01ms)
MFC에서 파일 복사
- CopyFileEx 사용
참고
- 윤성우 열혈 C 프로그래밍
- https://blog.naver.com/tipsware/221275416466
- https://copynull.tistory.com/120