IT 그리고 정보보안/Knowledge base

표준 입출력 라이브러리 (Standard I/O Library)

plummmm 2021. 4. 14. 13:53
반응형

standard i/o 라이브러리 즉, #include <stdio.h>에 정의된 라이브러리 함수들에 대해 알아볼 것이다.

얘네들은 UNIX관련 표준은 아닌데.. 왜냐면 다른 OS에서도 이 친구들을 사용할 수 있기 때문이다.

 

표준 I/O 라이브러리 들이 대단한 이유는.. 역사를 좀 타고 올라가보면

Mike Lesk라는 사람이 처음에 Portable I/O 라이브러리를 만들었는데,

데니스 리치님께서 1975년경에 이것을 크게 뜯어고쳤는데

이게 지금까지도 별로 수정된 부분없이 계속해서 쓰이고 있다는 점이다. 쩐다..

 

먼저 stdio를 공부하기 위해 스트림(stream)과 FILE 구조체에 대한 개념을 알아야 한다.

저번에 배운 File I/O 함수들, read, write, open 등의 함수들을 컨트롤하기 위해 파일 디스크립터를 사용했다면,

stdio에서는 스트림이라는 개념이 있다.

 

스트림이란 쉽게 말해 컴퓨터 프로그램과 입출력 환경과의 연결 통로? 정도로 볼 수 있는데.

FILE 이라고 불리는 파일 구조체를 가르키는 미리 정의된 포인터이다.

 

표준 입출력 스트림으로는 stdin, stdout, stderr 가 있는데, 기존의 표준 파일 디스크립터인 STDIN_FILENO, 

STDOUT_FILENO, STDERR_FILENO 와 같은 파일을 가르킨다.

 

#include <stdio.h>

FILE *stdin

FILE *stdout

FILE *stderr

 

자, 파일 디스크립터랑 비슷한 개념이라고 했으니.. 기존에 시스템 콜 함수 open을 호출하면 파일 디스크립터를 리턴하였다. 그럼 표준 입출력에서는? fopen 함수를 이용하여 파일을 열면 FILE 구조체의 포인터를 반환한다.

FILE 구조체는 스트림을 관리하기 위해 필요한 정보들을 담은 구조체이다. 

 

버퍼링(Buffering)

표준 입출력으로 넘어오면서 버퍼링이라는 개념이 생겼다.

우리가 일전에 알고 있던.. 동영상을 재생했을 때 생기는 버퍼링과 같은 맥락이라고 보셈.

표준 입출력에서 버퍼링을 사용하는 이유는 궁극적으로 read와 write 횟수를 최소화시키기 위함이다.

즉 프로그램의 퍼포먼스 향상에 그 목적이 있는 것이다.

 

버퍼링은 총 3가지로 나뉜다.

 

1. 전체 버퍼링 (Fully buffering)

표준 입출력 버퍼가 가득 찼을 때만 입출력이 일어나도록 하는 것이다.

표준 입출력 함수들은 디스크에 있는 모든 파일들에게 이 전체 버퍼링을 적용한다.

전체 버퍼링에 사용되는 버퍼는 한 스트림에 대해 처음 입출력이 일어날 때 malloc으로 할당한다.

버퍼가 가득 차면 전체 버퍼링이 적용된 파일은 flush하여 디스크에 기록하게 된다.

 

2. 라인 버퍼링 (Line buffering)

줄 단위 즉, 개행문자(\n)를 만났을 때 입출력을 수행하는 것이다.

라인 버퍼링은 터미널을 가르키는 스트림에서 일어난다.

라인 버퍼링을 하는 중에 버퍼가 가득차면 flush 된다.

 

3. 버퍼링 없음 (Unbuffered)

버퍼링을 안하는 거다. 표준 에러같은 경우가 버퍼링이 없다.

 

이렇게 기본 버퍼링 방식이 있는데, 이게 적절하지 않다고 생각되는 스트림은 버퍼 방식을 변경할 수 있다.

setbuf, setvbuf 함수를 이용해서 변경한다.

 

setbuf 함수로는 버퍼를 켜거나 끌 수 있다.

setvbuf 함수는 버퍼링의 종류를 구체적으로 변경할 수 있다. 

두 함수의 동작 방식에 대한 내용을 아래 표를 보고 확인하자.

 

웬만하면 시스템이 지정해주는 버퍼를 지가 알아서 할당하게 해두어야 나중에 스트림을 닫을 때

버퍼 해제도 지 알아서 하기 때문에, 괜히 자기가 지정해서 쓰는 일은 지양하자.

 

그리고 버퍼의 내용을 flush하는 함수도 있지. fflush함수이다.

 

지정한 스트림에 대해 flush 작업을 하는데, fp가 NULL이면 출력 스트림 전부가 flush된다.

 

fopen(), freopen(), fdopen(), fclose()

이제 스트림과 버퍼링에 대해 알았으니 실제로 스트림을 여는 (파일을 여는) 표준 입출력 라이브러리에 대해 알아보자.

fopen계열 함수를 이용한다. 기존의 입출력 함수에서 표준 입출력 함수는 이름에 f만 붙이면 되는 것 같다.

fopen, freopen, fdopen 함수에 대해 알아보자.

 

fopen 함수는 단순히 지정된 파일을 open하는 함수이다.

 

freopen은 사용자가 open할 파일의 스트림을 지정할 수 있다. 이미 열려 있으면 닫고 연다. 깡패다.

이놈은 주로 open할 파일의 스트림을 stdin,stdout, stderr 로 열 때 자주 사용된다.

 

fdopen은 기존에 있는 파일 디스크립터를 가져와 표준 입출력 스트림과 연관 시킨다.

파이프나 네트워크 통신 채널을 생성하는 함수들이 표준 입출력 스트림으로 open이 되지 않으므로

그녀석들이 반환하는 파일 디스크립터를 스트림에 연관시킬 때 사용하는 함수이다.

 

열린 스트림을 닫을 때는 fclose를 사용하면 된다.

 

파일이 닫히기 전에 버퍼에 있는 출력자료는 flush된다. 입력자료는 전부 폐기됨.

프로세스가 정상적으로 종료된다면 모든 자료가 flush됨

 

문자 단위 표준 입출력

이제 드디어 표준 입출력에 대한 함수들을 공부한다.

fopen으로 파일 스트림을 열었으니 이제 읽기 쓰기 작업을 해야 할 것이다. 

근데 표준 입출력에는 일정한 단위로 작업을 한다. 함 알아보자.

 

문자 단위 입출력 (Character-at-a-time I/O)

한번에 하나의 문자를 읽거나 쓰는 것이다. 먼저 한 문자씩 읽어오는 함수를 보자.

getchar()와 getc(stdin) 은 같은 기능을 한다. fgetc는 메크로로 정의될 수 있다는 차이점이 있다.

설명할게 별로 없다 그냥 한문자 씩 읽어 오는 함수들이다. 

 

다음은 쓰기함수들이다.

 

getc계열 함수들과 똑같다. 메크로 정의 가능하냐 불가능하냐의 차이. 

putchar(c) = putc(c, stdout) 이다.

 

다만 여기서 하나 짚고 넘어가야 되는 내용이 있다. 분명 putc를 사용할 때

 

putc('a', stdout); 

이런 식으로 사용한단 말이다. 근데 왜 형이 int형일까?

문자 자체는 unsigned char로 취급되는데, 반환값은 int형으로 변환한다.

이유는 반환값이 모든 문자의 값을 표현할 수 있어야 될 뿐 아니라 오류 or EOF 만났을 때

상수가 반드시 음수가 쓰여야 하기 때문이다.

 

그리고 스트림에서 읽어온 문자를 다시 되돌려 놓을 수도 있다.

ungetc()함수를 이용하여 하면된다.

꼭 읽은 문자가 되돌릴 문자와 같을 필요는 없단다.

 

줄 단위 표준 입출력

줄 단위 입력 함수부터 알아보자.

gets 함수는 stdin의 입력을 읽는 함수이다. fgets 함수는 fp가 가르키는 스트림을 읽는다. 버퍼와 읽어올 바이트크기까지 지정해준다. 근데 gets는 쓰지마라. 읽어 들일 바이트 수를 지정할 수 없기 때문에 버퍼 오버플로우가 발생할 수 있는 함수. 시스템 해킹 공부하는 사람이야 이 함수에 대해 잘 알거라고 생각함.

다음은 출력 함수를 보자.

fputs 함수는 fp로 지정된 스트림에 주어진 널 종료 문자열을 기록한다. 근데 라인 단위로 기록하는데

꼭 \n 개행문자가 아니라도 된다. 즉, 줄 단위 출력에만 사용하는게 아니라는말.

puts 함수는 주어진 널 종료 문자열을 표준 출력에 기록하는데, 실제로 널 대신 \n을 기록한다.

 

 

이진 표준 입출력

한번에 다 갖다가 읽고 쓰는 이진 표준 입출력에 대해 알아보자.

I/O 작업을 수행할 때 구조체 전체를 한번에 다 처리하는 것이다.

 

size * nobj 만큼 읽고 쓰는 함수들이다. 반환값은 작업한 객체수를 반환.

말이 너무 어렵다. 풀어 써보면 이런 식으로..

 

fwrite(쓸 문자열의 주소, 문자열 사이즈, 반복 횟수, 쓸 파일 포인터)

fread(저장할 문자열의 주소, 문자열 사이즈, 반복 횟수, 읽을 파일 포인터)

 

이진 표준 입출력의 한계는 이렇게 입출력된 자료는 반드시 같은 시스템에서만 읽고 쓰기를 할 수 있다는 점

구조체 안의 맴버의 offset 값이 컴파일러 마다, 시스템 마다 다를 수 있다.

그리고 시스템 마다 byte ordering이 다르므로

 

 

참고 : http://devkyu.tistory.com/

 

반응형