시스템 수준 입출력
기본적인 입출력
C 언어에서는 기억장치에 저장되는 파일을 다루는 것과 마찬가지 방식으로 입출력 장치를 다룬다. 따라서 키보드, 모니터와 같은
대부분의 콘솔 장치도 C 프로그램에서는 자동으로 열리는 파일처럼 다뤄진다. C 언어에서는 stdin 표준 스트림을 통해 입력 장치를
다루며, stdout 표준 스트림을 통해 출력 장치를 다룬다.
스트림(stream)
C 프로그램은 파일이나 콘솔의 입출력을 직접 다루지 않고, 스트림(stream)이라는 것을 통해 다룬다. 스트림(stream)이란 실제의
입력이나 출력이 표현된 데이터의 이상화된 흐름을 의미한다. 즉, 스트림은 운영체제에 의해 생성되는 가상의 연결 고리를 의미한다.
C 언어에서 기본적으로 제공되는 표준 스트림(Standard Stream)은 표준 입력 스트림(stdin), 표준 출력 스트림(stout),
표준 오류 스트림(stderr) 세 가지가 있다.
EOF(End Of File)
운영체제에서 파일의 끝을 탐지하는 방법은 운영체제마다 약간씩 다르다. 하지만 C언어는 운영체제와 상관없이, 파일의 끝에
도달했을 때 언제나 특별한 값을 반환하도록 한다. 그 값을 EOF(End Of File)라고 하며, 실제로 이 값은 -1을 나타낸다.
파일뿐만 아니라 키보드를 통한 입력 시에도 입력의 끝을 알려주는 방법이 필요하다. 대부분의 유닉스(UNIX) 시스템에서는
라인의 시작 위치에서 Ctrl+D를 누르면 EOF를 발생시킬 수 있다.
버퍼(buffer)
우리가 사용하는 printf(), scanf(), puts() 함수 등은 C언어의 표준 입출력 함수다. 이러한 표준 입출력 함수를 사용할 때에는
버퍼(buffer)라는 임시 메모리 공간을 사용한다.
버퍼를 사용하면서 얻을 수 있는 장점은 다음과 같다.
1. 문자를 하나씩 전달하는 것이 아닌 묶어서 한 번에 전달하므로, 전송 시간이 적게 걸려 성능이 향상된다.
2. 사용자가 문자를 잘못 입력했을 경우 수정을 할 수가 있다.
하지만 입력 작업에 버퍼를 사용하는 것이 반드시 좋은 것만은 아니다. 빠른 반응이 요구되는 게임과 같은 프로그램에서는 키를
누르는 즉시 바로 전달되어야만 한다. 이렇게 버퍼를 사용하는 입력과 버퍼를 사용하지 않는 입력은 서로 다른 용도로 사용된다.
따라서 목적에 맞게 버퍼의 사용 여부를 판단해야 한다.
버퍼링 방식
입출력에 사용되는 버퍼링에는 다음과 같이 두 가지 방식이 있다.
1. 완전 버퍼링(fully buffered)
2. 라인 버퍼링(line-buffered)
완전 버퍼링은 버퍼가 가득 차면 버퍼 안의 내용을 목적지로 보내는 방식이다. 이 방식은 보통 파일 입출력에서 사용된다.
라인 버퍼링은 입력된 문자 중 개행 문자가 나타날 때마다 버퍼 안의 내용을 목적지로 보내는 방식이다. 이 방식은 보통 키보드
입력에서 사용된다. 이러한 입력 작업뿐만 아니라 printf() 함수 등을 통해 모니터에 데이터를 출력할 때도 버퍼를 사용한다.
출력하고자 하는 데이터는 일단 출력 버퍼에 저장되었다가 출력 스트림을 통해 모니터로 전송된다.
파일(file)이란?
파일(file)이란 의미 있는 정보를 담고 있으며, 이름을 가지고 있는 저장 장치상의 논리적인 단위를 의미한다. C언어에서는 이러한
파일을 바이트별로 따로 읽을 수 있는 연속적인 바이트의 집합으로 취급한다.
파일의 종류
컴퓨터는 파일을 다음과 같이 두 가지 종류로 나누어서 다룬다.
1. 텍스트 파일(text file)
2. 바이너리 파일(binary file)
텍스트 파일은 사람이 알아볼 수 있는 문자열로 이루어진 파일을 의미한다. 프로그램이 이 파일의 데이터를 읽거나 쓸 때는 포맷
형식에 따라 데이터의 변환이 일어난다. 바이너리 파일은 데이터의 저장과 처리를 목적으로 0과 1의 이진 형식으로 인코딩된 파일을
의미한다. 프로그램이 이 파일의 데이터를 읽거나 쓸 때는 데이터의 어떠한 변환도 일어나지 않는다.
파일의 입출력
C언어에서 콘솔 장치에 대한 스트림은 프로그램 실행 시 자동으로 생성되며, 프로그램 종료 시 자동으로 소멸한다. 하지만 파일과의
연결을 위한 스트림은 사용자가 직접 생성하고 소멸시켜야 한다. C 언어에서 파일에 대한 입출력 동작 순서는 아래와 같다.
1. 파일과의 스트림 생성
2. FILE 구조체 변수의 포인터를 이용한 작업 진행
3. 파일과의 스트림 종결
fopen() 함수
fopen() 함수는 파일을 열어주는 함수이다. 파일을 연다는 것은 파일과의 입출력을 위한 스트림을 생성한다는 의미이다.
#include <stdio.h>
FILE *fopen(const char * restrict filename, const char * restrict mode);
fopen() 함수의 첫 번째 인수는 열고자 하는 파일의 이름과 그 경로를 가지고 있는 문자열이다. 두 번째 인수는 파일을 여는 데 사용할
모드를 지정하는 문자열이다. 프로그램이 성공적으로 파일을 열면, fopen() 함수는 FILE 구조체 변수의 포인터를 반환한다. 만약에
해당 파일을 열 수 없으면, fopen() 함수는 널 포인터를 반환한다.
모드 문자열
모드 문자열은 파일의 사용 용도를 결정하는 문자열과 파일의 데이터를 어떤 방식으로 입출력할지 결정하는 문자열로 구성된다.
우선 파일의 사용 용도를 결정하는데 사용할 수 있는 모드 문자열은 다음과 같다.
1. r (read mode) : 읽기 전용 모드
2. w (write mode) : 쓰기 전용 모드
3. a (append mode) : 추가 모드
그리고 파일의 데이터를 어떤 방식으로 입출력할지를 결정하는 사용할 수 있는 모드 문자열은 다음과 같다.
1. t (text mode) : 해당 파일의 데이터를 텍스트 파일로 인식하고 입출력함.
2. b (binary mode) : 해당 파일의 데이터를 바이너리 파일로 인식하고 입출력함.
fclose() 함수
fclose() 함수는 파일을 닫아주는 함수이다. 파일을 닫는다는 것은 파일과의 입출력을 위해 생성한 스트림을 소멸시키는 것을
의미한다. 즉, 버퍼에 남아있는 데이터를 파일로 완전히 내보내고, 파일 입출력을 위해 내부적으로 생성했던 FILE 구조체를
해제한다. C 언어에서 다 사용한 파일은 반드시 fclose() 함수를 사용하여 닫아줘야 한다.
#include <stdio.h>
int fclose(FILE *stream);
fclose() 함수는 인수로 닫고자 하는 파일을 가리키는 FILE 구조체 변수의 포인터를 전달받는다. 이 함수는 파일을 성공적으로
닫으면 0을 반환하고, 해당 파일을 닫지 못했다면 EOF를 반환한다.