스트림 관련 라이브러리 함수
stdio
-
시스템 콜만을 이용해서, 프로그램을 작성할 수 있지만, 시스템 콜만을 사용하여 더욱 복잡한 입출력 기능을 구현하려 한다면 다음과 같은 문제에 봉착하게 된다.
-
첫째, 시스템 콜은 바이트 단위로만 읽고 쓸 수 있다. 우리에게 더 익숙한 단위, 예를 들면 문자 단위나 줄 단위로 문자열을 처리할 수 있다면 더욱 편리할 것이다.
-
두 번째로 성능의 문제가 발생한다. 예를 들어서 10 바이트나 20바이트 단위로
read()
,write()
호출을 반복한다면 아무리 최신 컴퓨터로도 시간이 오래 걸릴 수 밖에 없다. 스트림에 연결된 장치에 따라서 다르지만, 대체로1KB
단위로 시스템 콜을 해야 효율적이다. -
이러한 문제를 피해서 편리하게 사용할 수 있게 만들어진 것이 바로 표준 입출력 라이브러리(standard I/O library)이다. 줄여서
stdio
라고 한다.stdio
는libc
의 큰 비중을 차지하는 만큼 중요한 라이브러리이다.
버퍼링
-
stdio
는 커널 수준의 스트림을 쉽게 사용할 수 있는 인터페이스를 제공한다. 예를 들어 바이트 단위로 읽고 쓰는 함수나, 줄 단위로 읽고 쓰는 함수를 제공한다. 또한 숫자나 문자열을 포맷에 맞게 출력하는 함수도 있다. -
시스템 콜
read()
는 스트림에서 우리가 지정한buffer
에 지정한 크기만큼 읽어 들이는 반면에,stdio
는 독자적인 버퍼를 사용한다. 여기서 버퍼란 일시적으로 데이터를 저장하는 장소를 말한다. -
그래서 시스템 콜
read()
를 사용하여, 적당한 크기의 데이터를 버퍼에 읽어들이고, 프로그램에서 요구하는 만큼을 다시 반환해준다. 예를 들어 1바이트를 읽어 달라는 요청을 받으면 버퍼에서 1바이트만 다시 반환해준다. 이렇게 버퍼를 이용하여 데이터를 주고 받는 것을 버퍼링이라고 한다. -
시스템 콜로 1바이트를 연속해서 요청하면 속도가 안나는 반면에,
stdio
를 사용한다면 성능의 저하 없이 바이트 단위로 읽는 것이 가능하다.
버퍼링 모드
- 데이터를 쓸 때도 읽을 때와 마찬가지로 버퍼를 사용한다. 바이트 단위 또는 줄 단위의 데이터를 전달 받아 버퍼가 꽉 차면 시스템 콜
write()
를 호출한다.
예외
그러나 몇가지 중요한 예외가 있다.
1. 스트림이 단말에 연결된 경우에는 버퍼가 가득찰 때까지 기다리지 않고
개행 (\n)을 만나는 시점에서 write()를 실행한다. 이유는, 반대편에 모니터와
같은 단말이 있다면 사람이 출력을 보고 있을 가능성이 높기 때문이다.
버퍼가 가득차기 까지는 오랜 시간이 걸릴 수도 있기 때문에 적절한 순간에
바로 출력해주는 것이 프로그램의 응답이 빨라지고 사용자의 사용성도 좋아진다.
2. 스트림이 비버퍼링 모드(unbuffered mode)로 되어 있는 경우이다. 비버퍼링 모드로 설정된
stdio 스트림에 데이터를 쓰면 버퍼링 없이 즉시 write()가 수행된다. setvbuf()로 설정할 수 있다.
3. 표준 예외 출력에 해당하는 stderr에 대한 출력이다. stderr은 예외적으로 처음부터
비버퍼링 모드다. 그 이유는 표준 에러 출력의 경우 에러 메시지나 디버깅 정보를 출력하는데 사용되기 때문에 발생한 시점에서 바로 출력하는 것이 바람직하기 때문이다.
FILE 타입
-
시스템 콜 레벨에서는 스트림을 지정하기 위해 파일 디스크립터라는 것을 사용했었다. 한편, stdio에서는 비슷한 역할을 하기 위해
FILE
타입에 대한 포인터를 사용한다. -
FILE
타입은typedef
으로 정의되어 있어, 그 안에는 파일 디스크립터와 앞서 설명한stdio
버퍼의 내부 정보를 포함하고 있다. 라이브러리를 사용하는 입장에서는FILE
타입의 내부 구조를 몰라도 사용할 수 있다.
stdio의 표준 입출력
- 시스템 콜을 사용할 때, 표준 입출력 스트림을 지정하기 위해 예약된 정수가 있었는데,
stdio
에도 이에 대응하여,FILE*
타입의 변수가 있다.
파일 디스크립터 | 정식 명칭 | stdio 변수명 | 의미 |
---|---|---|---|
0 | STDIN_FILENO | stdin | 표준 입력 |
1 | STDOUT_FILENO | stdout | 표준 출력 |
2 | STDERR_FILENO | stderr | 표준 에러 출력 |
fopen(3)
표준 입출력 이외의 스트림에 대한 FILE
을 여는 것도 물론 가능하다. 이때는 fopen()
이라는 API
를 사용한다. 이것은 시스템 콜 open()
에 대응된다.
#include <stdio.h>
FILE *fopen(const char *path, const char *mode);
-
fopen()
은 첫 번째 인자path
로 지정한 파일에 대한 스트림을 만들고 그것을 관리하는FILE
포인터를 반환한다. 만약 실패한 경우NULL
을 리턴하고, 원인을 나타내는 상수를errno
에 설정한다. -
그리고 두 번째 인자인
mode
에는 다음과 같은 옵션을 지정할 수 있다.
`fclose(3)
- 시스템 콜
open()
에 대응하는 API가fopen()
이라면close()
에 대응하는 API는fclose()
이다.
#include <stdio.h>
int fclose(FILE *stream);
문자열 입출력
-
파일은 바이트의 연속이다. 그런데 그 바이트의 연속을 문자의 연속으로 본다면 파일은 곧 줄의 연속이라고 볼 수 있다.
-
리눅스 시스템에서는 ‘줄’은
'\n'
를 기준으로 줄이 나뉜다. -
또한 파일의 마지막이나 스트림으로부터의 입력이 끝나는 지점에
'\n'
이 없어도 하나의 줄로 간주한다.